@stacksjs/rpx 0.11.44 → 0.11.46
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 +231 -239
- package/dist/chunk-086aa2f7.js +1 -0
- package/dist/chunk-nnnptjfw.js +49 -0
- package/dist/chunk-t3s94va5.js +1 -0
- package/dist/chunk-z0swphpg.js +253 -0
- package/dist/dns-state.d.ts +28 -2
- package/dist/index.js +2 -2
- package/dist/proxy-handler.d.ts +12 -1
- package/dist/proxy-pool.d.ts +1 -0
- package/package.json +1 -1
- package/dist/chunk-7sytrykf.js +0 -1
- package/dist/chunk-bvq3wmhm.js +0 -1
- package/dist/chunk-mbba4csc.js +0 -252
- package/dist/chunk-w4t1nar9.js +0 -49
package/dist/dns-state.d.ts
CHANGED
|
@@ -8,8 +8,26 @@ export declare function clearDnsState(rpxDir: string): Promise<void>;
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function normalizeDevDomain(raw: string): string | null;
|
|
10
10
|
/**
|
|
11
|
-
* macOS resolver basename for a dev domain.
|
|
12
|
-
*
|
|
11
|
+
* macOS resolver basename for a dev domain.
|
|
12
|
+
*
|
|
13
|
+
* `/etc/resolver/<name>` captures that name **and every subdomain of it**, and a
|
|
14
|
+
* captured name resolves only through rpx's DNS server. When that server is not
|
|
15
|
+
* answering, every captured name stops resolving on the machine entirely.
|
|
16
|
+
*
|
|
17
|
+
* So the basename is the exact hostname for a real, publicly registered domain.
|
|
18
|
+
* Collapsing `dashboard.stacksjs.com` to `stacksjs.com` claimed the apex and
|
|
19
|
+
* with it every sibling - `mail.stacksjs.com`, `www.`, all of them - and when
|
|
20
|
+
* rpx stopped answering, a mail client pointed at the real, running mail server
|
|
21
|
+
* simply timed out, because the name no longer resolved. `dig` and `host` kept
|
|
22
|
+
* working, which is what made it look like a server problem: they query DNS
|
|
23
|
+
* directly and never consult `/etc/resolver`.
|
|
24
|
+
*
|
|
25
|
+
* Under a dev-only TLD the collapse is kept, because that is what makes one
|
|
26
|
+
* `/etc/resolver/postline.test` serve `api.postline.test` too, and nothing real
|
|
27
|
+
* can be shadowed there.
|
|
28
|
+
*
|
|
29
|
+
* This is the same reasoning that already forbids whole-TLD files like
|
|
30
|
+
* `/etc/resolver/com`, applied one label further down.
|
|
13
31
|
*/
|
|
14
32
|
export declare function resolverBasenameForDomain(raw: string): string | null;
|
|
15
33
|
export declare function resolverBasenamesForDomains(domains: string[]): string[];
|
|
@@ -18,6 +36,14 @@ export declare const DNS_STATE_VERSION: 1;
|
|
|
18
36
|
export declare const RPX_DNS_STATE_FILE: 'dns-state.json';
|
|
19
37
|
/** Single-label /etc/resolver files created by older rpx versions (whole-TLD hijack). */
|
|
20
38
|
export declare const LEGACY_TLD_RESOLVER_LABELS: readonly ['com', 'test', 'dev', 'app', 'page', 'local', 'localhost', 'example', 'invalid'];
|
|
39
|
+
/**
|
|
40
|
+
* Top-level domains that exist only for development.
|
|
41
|
+
*
|
|
42
|
+
* Nothing real resolves under these, so collapsing to the registrable base is
|
|
43
|
+
* free: taking all of `postline.test` cannot shadow a name anyone else needs.
|
|
44
|
+
* A public TLD is the opposite case - see `resolverBasenameForDomain`.
|
|
45
|
+
*/
|
|
46
|
+
export declare const DEV_ONLY_TLDS: Set<string>;
|
|
21
47
|
export declare interface DnsState {
|
|
22
48
|
version: typeof DNS_STATE_VERSION
|
|
23
49
|
resolvers: string[]
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{$ as
|
|
2
|
-
`,z=(G)=>{let Y=r(G);return S.has(Y)||w.some((J)=>m(Y,J))},$=(G)=>{let Y=NR(G);if(!z(Y))return;let J="/";try{J=new URL(G.url).pathname}catch{}if(K.some((B)=>J.startsWith(B)))return;if(zR(G.headers.get(x),R.value))return;return new Response(N,{status:403,headers:{"content-type":"text/plain"}})};return $.protects=z,$}var ax={name:"@stacksjs/rpx",type:"module",version:"0.11.44",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.13"},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 px=ax.version;var o=new s,GR=new ux("0.0.0.0"),b=new Set,Rx=new Set,Qx=!1,e=null,Zx=null;async function p(R){if(Qx)return _("cleanup","Cleanup already in progress, skipping",R?.verbose),Zx||Promise.resolve();Qx=!0,_("cleanup","Starting cleanup process",R?.verbose),Zx=new Promise((x)=>{e=x});try{await o.stopAll(R?.verbose),j.info("Shutting down proxy servers...");let x=[],S=Array.from(b).map((w)=>new Promise((K)=>{let N=w;try{if(typeof N.stop==="function")N.stop(!0),_("cleanup","Bun server stopped",R?.verbose),K();else if(typeof N.close==="function")N.close(()=>{_("cleanup","Server closed successfully",R?.verbose),K()});else K()}catch(z){_("cleanup",`Error stopping server: ${z}`,R?.verbose),K()}}));x.push(...S),b.clear();for(let w of Rx)Nx(w);if(Rx.clear(),R?.hosts&&R.domains?.length){_("cleanup","Cleaning up hosts file entries",R?.verbose),_("cleanup",`Original domains for cleanup: ${JSON.stringify(R.domains)}`,R?.verbose);let w=R.domains.filter((K)=>{if(K==="test.local")return!0;return K!=="localhost"&&!K.startsWith("localhost.")&&K!=="127.0.0.1"});if(_("cleanup",`Filtered domains for cleanup: ${JSON.stringify(w)}`,R?.verbose),w.length>0)j.info("Cleaning up hosts file entries..."),x.push(Px(w,R?.verbose).then(()=>{_("cleanup",`Removed hosts entries for ${w.join(", ")}`,R?.verbose)}).catch((K)=>{_("cleanup",`Failed to remove hosts entries: ${K}`,R?.verbose),j.warn(`Failed to clean up hosts file entries for ${w.join(", ")}:`,K)}))}if(R?.certs&&R.domains?.length){_("cleanup","Cleaning up SSL certificates",R?.verbose),j.info("Cleaning up SSL certificates...");let w=R.domains.map(async(K)=>{try{await Ix(K,R?.verbose),_("cleanup",`Removed certificates for ${K}`,R?.verbose)}catch(N){_("cleanup",`Failed to remove certificates for ${K}: ${N}`,R?.verbose),j.warn(`Failed to clean up certificates for ${K}:`,N)}});x.push(...w)}await Promise.allSettled(x),_("cleanup","All cleanup tasks completed successfully",R?.verbose),j.success("All cleanup tasks completed successfully")}catch(x){_("cleanup",`Error during cleanup: ${x}`,R?.verbose),j.error("Error during cleanup:",x)}finally{if(e)e();e=null,Qx=!1;let x=R&&"vitePluginUsage"in R&&R.vitePluginUsage===!0;if(Q.env.NODE_ENV!=="test"&&Q.env.BUN_ENV!=="test"&&!x)Q.exit(0)}return Zx}var kx=!1;function ex(R){if(kx){_("signal",`Received second ${R} signal, forcing exit`,!0),Q.exit(1);return}kx=!0,_("signal",`Received ${R} signal, initiating cleanup`,!0),p().catch((x)=>{_("signal",`Cleanup failed after ${R}: ${x}`,!0),Q.exit(1)}).finally(()=>{kx=!1})}Q.once("SIGINT",()=>ex("SIGINT"));Q.once("SIGTERM",()=>ex("SIGTERM"));Q.on("uncaughtException",(R)=>{j.error("Uncaught exception (continuing):",R)});Q.on("unhandledRejection",(R)=>{j.error("Unhandled rejection (continuing):",R)});async function a(R,x,S,w=5){_("connection",`Testing connection to ${R}:${x} (retries left: ${w})`,S);let K=15000,N=Date.now();if(Q.env.RPX_BYPASS_CONNECTION_TEST==="true"){_("connection",`Bypassing connection test for ${R}:${x} due to RPX_BYPASS_CONNECTION_TEST flag`,S);return}let z=()=>new Promise(($,G)=>{let Y=sx.connect({host:R,port:x,timeout:3000});Y.once("connect",()=>{_("connection",`Successfully connected to ${R}:${x}`,S),Y.end(),$()}),Y.once("timeout",()=>{_("connection",`Connection to ${R}:${x} timed out`,S),Y.destroy(),G(Error("Connection timed out"))}),Y.once("error",(J)=>{_("connection",`Failed to connect to ${R}:${x}: ${J}`,S),Y.destroy(),G(J)})});try{await z()}catch($){let G=$;if(Date.now()-N>K){_("connection",`Connection test timed out after ${K}ms, but continuing anyway`,S),j.warn(`Connection test to ${R}:${x} timed out, but RPX will try to proceed anyway.`);return}if(G.code==="ECONNREFUSED"&&w>0)return _("connection",`Connection refused, server might be starting up. Retrying in 2 seconds... (${w} retries left)`,S),await new Promise((J)=>setTimeout(J,2000)),a(R,x,S,w-1);if(w>0)try{_("connection",`Trying HTTP request to ${R}:${x}`,S),await new Promise((J,B)=>{let X=Kx.request({hostname:R,port:x,path:"/",method:"HEAD",timeout:5000},(V)=>{_("connection",`Received HTTP response with status: ${V.statusCode}`,S),J()});X.on("error",(V)=>B(V)),X.on("timeout",()=>{X.destroy(),B(Error("HTTP request timed out"))}),X.end()}),_("connection",`HTTP request to ${R}:${x} succeeded`,S);return}catch(J){return _("connection",`HTTP request to ${R}:${x} failed: ${J}`,S),_("connection",`Retrying socket connection in 2 seconds... (${w} retries left)`,S),await new Promise((B)=>setTimeout(B,2000)),a(R,x,S,w-1)}let Y=`Failed to connect to ${R}:${x} after ${5-w} attempts: ${G.message}`;_("connection",`${Y}. To bypass this check set RPX_BYPASS_CONNECTION_TEST=true`,S),j.warn(Y),j.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 Lx(R){_("server",`Starting server with options: ${q(R)}`,R.verbose);let x=u(R.from),S=new URL(x.startsWith("http")?x:`http://${x}`),w=new URL((R.to?.startsWith("http")?R.to:`http://${R.to}`)||"rpx.localhost"),K=Number.parseInt(S.port)||(S.protocol.includes("https:")?443:80),N=[w.hostname];if(Ox(R)&&!w.hostname.includes("localhost")&&!w.hostname.includes("127.0.0.1")){_("hosts",`Checking if hosts file entry exists for: ${w.hostname}`,R?.verbose);try{if(!(await L(N,R.verbose))[0]){j.info(`Adding ${w.hostname} to hosts file...`),j.info("This may require sudo/administrator privileges");try{await P(N,R.verbose)}catch(G){if(j.error("Failed to add hosts entry:",G.message),j.warn("You can manually add this entry to your hosts file:"),j.warn(`127.0.0.1 ${w.hostname}`),j.warn(`::1 ${w.hostname}`),Q.platform==="win32")j.warn("On Windows:"),j.warn("1. Run notepad as administrator"),j.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");else j.warn("On Unix systems:"),j.warn("sudo nano /etc/hosts")}}else _("hosts",`Host entry already exists for ${w.hostname}`,R.verbose)}catch($){j.error("Failed to check hosts file:",$.message)}}try{await a(S.hostname,K,R.verbose)}catch($){_("server",`Connection test failed: ${$}`,R.verbose),j.error($.message),j.warn("Continuing with proxy setup despite connection test failure..."),j.info("If you need to bypass connection testing, set environment variable RPX_BYPASS_CONNECTION_TEST=true")}let z=R._cachedSSLConfig||null;if(R.https)try{if(R.https===!0)R.https=wx({...R,to:w.hostname});if(z=await c({...R,to:w.hostname,https:R.https}),!z){if(_("ssl",`Generating new certificates for ${w.hostname}`,R.verbose),await _x({...R,from:S.toString(),to:w.hostname,https:R.https}),z=await c({...R,to:w.hostname,https:R.https}),!z)throw Error(`Failed to load SSL configuration after generating certificates for ${w.hostname}`)}}catch($){throw _("server",`SSL setup failed: ${$}`,R.verbose),$}_("server",`Setting up reverse proxy with SSL config for ${w.hostname}`,R.verbose),await YR({...R,from:x,originalFrom:R.from||x,to:w.hostname,fromPort:K,sourceUrl:{hostname:S.hostname,host:S.host},ssl:z})}async function XR(R,x,S,w,K,N,z,$,G,Y,J,B){_("proxy",`Creating proxy server ${R} -> ${x} with cleanUrls: ${$}`,z);let X=zx(J??w.host,B);Ax(X);let V=[{host:x,route:{sourceHost:w.host,upstreamPool:X,cleanUrls:$||!1,changeOrigin:G||!1,basePath:"/",auth:Y}}];if(!xx({routeEntries:V,listenPort:S,sslConfig:K,originGuard:null,verbose:z??!1}))throw Nx(X),Error(`Failed to start proxy server for ${x} on port ${S}`);Rx.add(X),JR({from:R,to:x,vitePluginUsage:N,listenPort:S,ssl:!!K,cleanUrls:$,verbose:z})}async function YR(R){_("setup",`Setting up reverse proxy: ${q(R)}`,R.verbose);let{from:x,originalFrom:S,to:w,sourceUrl:K,ssl:N,verbose:z,cleanup:$,vitePluginUsage:G,changeOrigin:Y,cleanUrls:J}=R,B=80,X=443,V=xR(),f=R.portManager||GR,O=Ox(R);try{if(O&&w&&!w.includes("localhost")&&!w.includes("127.0.0.1")){if(!(await L([w],z))[0]){j.warn(`The hostname ${w} isn't in your hosts file. Adding it now...`);try{await P([w],z),j.success(`Added ${w} to your hosts file.`)}catch(h){j.error(`Failed to add ${w} to your hosts file: ${h}`),j.info(`You may need to manually add '127.0.0.1 ${w}' to your /etc/hosts file.`)}}}else if(O&&Q.platform!=="darwin"&&w&&w.includes("localhost")&&!w.match(/^(localhost|127\.0\.0\.1)$/)){if(!(await L([w],z))[0]){_("hosts",`${w} not found in hosts file, adding...`,z);try{await P([w],z)}catch(h){_("hosts",`Failed to add ${w} to hosts file: ${h}`,z)}}}if(N&&!f.usedPorts.has(B)){if(!await C(B,V,z))_("setup","Starting HTTP redirect server",z),ox(z),f.usedPorts.add(B);else if(_("setup","Port 80 is in use, skipping HTTP redirect",z),z)j.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available")}let Z=N?X:B,M=await C(Z,V,z),I;if(M){if(_("setup",`Port ${Z} is already in use`,z),z)j.warn(`Port ${Z} is already in use. This may be another instance of rpx or another service.`);if(Z===443){if(I=await f.getNextAvailablePort(3443,!0),_("setup",`Using port ${I} instead of ${Z}`,z),z)j.info(`Using port ${I} instead. Access your site at https://${w}:${I}`)}else if(I=await f.getNextAvailablePort(Z+1000,!0),_("setup",`Using port ${I} instead of ${Z}`,z),z)j.info(`Using port ${I} instead. Access your site at http://${w}:${I}`)}else I=Z,f.usedPorts.add(I),_("setup",`Using standard ${Z===443?"HTTPS":"HTTP"} port ${Z} for ${w}`,z);await XR(x,w,I,K,N,G,z,J,Y,jx(R.auth),S,R.loadBalancer)}catch(Z){_("setup",`Setup failed: ${Z}`,z),j.error(`Failed to setup reverse proxy: ${Z.message}`),p({domains:[w],hosts:typeof $==="boolean"?$:$?.hosts,certs:typeof $==="boolean"?$:$?.certs,verbose:z,vitePluginUsage:G})}}function ox(R,x=80,S=443,w,K,N){_("redirect",`Starting HTTP redirect server on port ${x}`,R);let z=Kx.createServer(($,G)=>{let Y=$.url?$.url.split("?",1)[0]:"";if(Y.startsWith("/.well-known/acme-challenge/")){if(K){let V=K.challengeStore.handlePath(Y);if(V!==void 0){_("redirect",`Serving on-demand ACME challenge ${Y}`,R),G.writeHead(200,{"content-type":"text/plain"}),G.end(V);return}}if(w){let V=fx(w,Y);if(V!=null){_("redirect",`Serving ACME challenge ${Y}`,R),G.writeHead(200,{"content-type":"text/plain"}),G.end(V);return}}if(K){G.writeHead(404,{"content-type":"text/plain"}),G.end("challenge not found");return}}let J=$.headers.host||"",B=J.includes(":")?J.slice(0,J.indexOf(":")):J;if(K&&B&&!K.hasCert(B)&&(!N||N(B)))K.ensureCert(B).catch(()=>{});let X=S===443?B:`${B}:${S}`;_("redirect",`Redirecting request from ${J}${$.url} to https://${X}`,R),G.writeHead(301,{Location:`https://${X}${$.url}`}),G.end()}).listen(x);b.add(z),_("redirect","HTTP redirect server started",R)}function $R(R){let x={...Sx,...R};if(_("proxy",`Starting proxy with options: ${q(x)}`,x?.verbose),x.viaDaemon){if(!x.from||!x.to){j.error("viaDaemon mode requires both `from` and `to`");return}g({proxies:[{id:x.id,from:x.from,to:x.to,path:x.path,cleanUrls:x.cleanUrls,changeOrigin:x.changeOrigin,pathRewrites:x.pathRewrites}],verbose:x.verbose}).catch((G)=>{j.error(`Failed to register with rpx daemon: ${G.message}`),Q.exit(1)});return}let S=x.to||"",w=S.split(".").pop()?.toLowerCase()||"",K=Q.platform==="darwin"&&S&&!S.includes("localhost")&&!S.includes("127.0.0.1"),N=["dev","app","page","new","day","foo"],z=["test","localhost","local","example","invalid"];if(K&&N.includes(w)&&x?.verbose)j.warn(`The .${w} TLD may not work reliably for local development`),j.info(` Google owns .${w} with HSTS preloading, which can bypass local DNS`),j.info(" Consider using a reserved TLD: .test, .localhost, or .local");if(K)import("./chunk-7sytrykf.js").then(({setupDevelopmentDns:G})=>{G({domains:[S],verbose:x.verbose}).then((Y)=>{if(Y)Promise.resolve().then(()=>{if(x.verbose)if(z.includes(w))j.success(`DNS server started for .${w} domains`);else j.success(`DNS server started for .${w} domains (hosts file entry also added)`)});else _("dns",`Could not start DNS server - ${S} may not resolve in browser`,x.verbose)})}).catch((G)=>{_("dns",`Failed to start DNS server: ${G}`,x.verbose)});let $={from:x.from,to:x.to,cleanUrls:x.cleanUrls,https:wx(x),cleanup:x.cleanup,vitePluginUsage:x.vitePluginUsage,changeOrigin:x.changeOrigin,verbose:x.verbose,regenerateUntrustedCerts:x.regenerateUntrustedCerts};_("proxy",`Server options: ${q($)}`,x.verbose),Lx($).catch((G)=>{_("proxy",`Failed to start proxy: ${G}`,x.verbose),j.error(`Failed to start proxy: ${G.message}`),p({domains:[x.to],hosts:typeof x.cleanup==="boolean"?x.cleanup:x.cleanup?.hosts,certs:typeof x.cleanup==="boolean"?x.cleanup:x.cleanup?.certs,verbose:x.verbose})})}function BR(R){return R?.verbose||!1}function Ox(R){if(R?.hostsManagement===!1)return!1;let x=R?.cleanup;if(x===!1)return!1;if(x&&typeof x==="object"&&x.hosts===!1)return!1;return!0}async function hx(R){let x={from:"localhost:5173",to:"rpx.localhost",https:!1,cleanup:{hosts:!0,certs:!1},vitePluginUsage:!1,verbose:!1,cleanUrls:!1,changeOrigin:!1,regenerateUntrustedCerts:!0};if(R)x={...x,...R};let S=BR(x),w=Ox(x);if(_("config",`Starting with config: ${q(x,2)}`,S),_("config",`Is multi-proxy? ${"proxies"in x}`,S),_("config",`Hosts management enabled? ${w}`,S),x.viaDaemon){let D="proxies"in x&&Array.isArray(x.proxies)?x.proxies.map((k)=>({id:k.id,from:k.from,to:k.to,path:k.path,cleanUrls:k.cleanUrls??x.cleanUrls,changeOrigin:k.changeOrigin??x.changeOrigin,pathRewrites:k.pathRewrites})):[{id:x.id,from:x.from,to:x.to??"rpx.localhost",path:x.path,cleanUrls:x.cleanUrls,changeOrigin:x.changeOrigin,pathRewrites:x.pathRewrites}];await g({proxies:D,verbose:S});return}if("proxies"in x&&Array.isArray(x.proxies)){_("servers",`Found ${x.proxies.length} proxies in config`,S);for(let A of x.proxies)if(A.start){let D=`${A.from}-${A.to}`;try{_("watch",`Starting command for ${D} with command: ${A.start.command}`,S),j.info(`Starting command for ${D}...`),await o.startProcess(D,A.start,S);let k=u(A.from),H=new URL(k.startsWith("http")?k:`http://${k}`),W=H.hostname||"localhost",T=Number(H.port)||80;try{await a(W,T,S),_("watch",`Dev server is ready at ${W}:${T}`,S)}catch(Xx){_("watch",`Connection check failed, but continuing with proxy setup: ${Xx}`,S),j.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(k){throw _("watch",`Failed to start command for ${D}: ${k}`,S),Error(`Failed to start command for ${D}: ${k}`)}}else _("watch",`No start command for proxy ${A.from} -> ${A.to}`,S)}else if("start"in x&&x.start){_("watch","Found start command in single proxy config",S);let A=`${x.from}-${x.to}`;try{if(x.start)_("watch",`Starting command: ${x.start.command}`,S),await o.startProcess(A,x.start,S);let D=u(x.from),k=new URL(D.startsWith("http")?D:`http://${D}`),H=k.hostname||"localhost",W=Number(k.port)||80;try{await a(H,W,S),_("watch",`Dev server is ready at ${H}:${W}`,S)}catch(T){_("watch",`Connection check failed, but continuing with proxy setup: ${T}`,S),j.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(D){throw _("watch",`Failed to run start command: ${D}`,S),Error(`Failed to run start command: ${D}`)}}else _("watch","No start command found in config",S);let K="proxies"in x&&Array.isArray(x.proxies)?x.proxies[0]?.to:("to"in x)?x.to:"rpx.localhost";if(Q.platform!=="win32"&&(x.https||w)){if(!dx())try{_("sudo","Pre-acquiring sudo credentials for privileged operations",S),KR("sudo -v",{stdio:"inherit"})}catch{_("sudo","Could not pre-acquire sudo credentials",S)}}let N=[];if(x.productionCerts){if(N=await Ux(x.productionCerts,S),N.length>0)_("ssl",`Using ${N.length} production SNI cert(s): ${N.map((A)=>A.serverName).join(", ")}`,S)}if(x.https){let A=N.length>0?null:await c(x);if(!A&&N.length===0){if(_("ssl",`No valid or trusted certificates found for ${K}, generating new ones`,x.verbose),await _x(x),A=await c(x),!A)throw Error(`Failed to load SSL certificates after generation for ${K}`)}else _("ssl",`Using existing and trusted certificates for ${K}`,x.verbose);x._cachedSSLConfig=A}let z="proxies"in x&&Array.isArray(x.proxies)?x.proxies.map((A)=>({...A,https:x.https,cleanup:x.cleanup,cleanUrls:A.cleanUrls??("cleanUrls"in x?x.cleanUrls:!1),vitePluginUsage:x.vitePluginUsage,changeOrigin:A.changeOrigin??x.changeOrigin,verbose:S,_cachedSSLConfig:x._cachedSSLConfig})):[{from:"from"in x?x.from:"localhost:5173",to:"to"in x?x.to:"rpx.localhost",cleanUrls:"cleanUrls"in x?x.cleanUrls:!1,https:x.https,cleanup:x.cleanup,vitePluginUsage:x.vitePluginUsage,start:"start"in x?x.start:void 0,changeOrigin:x.changeOrigin,auth:"auth"in x?x.auth:void 0,verbose:S,_cachedSSLConfig:x._cachedSSLConfig}],$=z.map((A)=>A.to||"rpx.localhost"),G=N.length>0?N:x._cachedSSLConfig??null,Y=$.filter((A)=>A&&!A.includes("localhost")&&!A.includes("127.0.0.1")),J=["dev","app","page","new","day","foo"],B=["test","localhost","local","example","invalid"],X=[...new Set(Y.map((A)=>A.split(".").pop()?.toLowerCase()))],V=X.filter((A)=>!!A&&J.includes(A));if(V.length>0&&S)j.warn(`The following TLDs may not work reliably for local development: ${V.map((A)=>`.${A}`).join(", ")}`),j.info(" These TLDs have HSTS preloading which can bypass local DNS"),j.info(" Consider using reserved TLDs: .test, .localhost, or .local");if(w&&Q.platform==="darwin"&&Y.length>0){let{setupDevelopmentDns:A}=await import("./chunk-7sytrykf.js");if(await A({domains:Y,verbose:S})){if(S)if(X.every((H)=>!!H&&B.includes(H)))j.success(`DNS server started for ${X.map((H)=>`.${H}`).join(", ")} domains`);else j.success(`DNS server started for ${X.map((H)=>`.${H}`).join(", ")} domains (hosts file entries also added)`)}else _("dns","Could not start DNS server - custom domains may not resolve",S)}let f=async()=>{_("cleanup","Starting cleanup handler",x.verbose);try{let{tearDownDevelopmentDns:A}=await import("./chunk-7sytrykf.js");await A({verbose:x.verbose})}catch(A){_("cleanup",`Error stopping DNS server: ${A}`,x.verbose)}try{await o.stopAll(x.verbose)}catch(A){_("cleanup",`Error stopping processes: ${A}`,x.verbose)}await p({domains:$,hosts:typeof x.cleanup==="boolean"?x.cleanup:x.cleanup?.hosts,certs:typeof x.cleanup==="boolean"?x.cleanup:x.cleanup?.certs,verbose:x.verbose||!1})};Q.on("SIGINT",f),Q.on("SIGTERM",f);let O=x.singlePortMode===!0,Z=x.httpsPort??443,M=x.httpPort??80,I=x.originGuard?Dx(x.originGuard):null,i=!!G&&(z.length>1||O||N.length>0),h=!G&&O&&z.length>0;if(i&&G){_("proxies",`Creating shared HTTPS server for ${z.length} domains on port ${Z}`,S);let A=await ix(z,w,S),D=null,k=x.onDemandTls,H=k?.enabled?new cx({config:k,certsDir:k.certsDir??x.productionCerts?.certsDir??rx.join(Gx.homedir(),".stacks","rpx","on-demand-certs"),initial:N,verbose:S,onCertAdded:(U)=>{if(tx()==="restart")_("on-demand","certificate installed; restarting supervised gateway to reload TLS",S),setTimeout(()=>Q.kill(Q.pid,"SIGTERM"),10).unref();else Xx(U)}}):null,W=null,T=!1;async function Xx(U){if(W=U,T)return;T=!0;try{while(W){let d=W;if(W=null,_("proxies",`rebuilding :${Z} with ${d.length} SNI cert(s)`,S),D)b.delete(D),D.stop(!1);let y=!1;for(let v=0;!y&&v<60;v++){let vx=xx({routeEntries:A,listenPort:Z,sslConfig:d,originGuard:I,verbose:S});if(vx){D=vx,y=!0;break}await new Promise((SR)=>setTimeout(SR,Math.min(25*2**Math.min(v,4),500)))}if(!y)j.error(`rpx: CRITICAL — could not rebind :${Z} after cert issuance; HTTPS unbound until the next cert event or a gateway restart`)}}finally{T=!1}}if(!await C(M,"0.0.0.0",S)){let U=new Set(A.map((y)=>y.host)),d=(y)=>U.has(y)||[...U].some((v)=>m(y,v));ox(S,M,Z,x.acmeChallengeWebroot,H,d)}if(await C(Z,"0.0.0.0",S)){if(_("proxies",`Port ${Z} is already in use, cannot start shared proxy`,S),S)j.warn(`Port ${Z} is in use. Shared HTTPS proxy cannot start.`);return}let RR=H&&H.sniEntries().length>0?H.sniEntries():G;if(D=xx({routeEntries:A,listenPort:Z,sslConfig:RR,originGuard:I,verbose:S}),!D){j.error(`Shared HTTPS proxy failed to bind :${Z}; not exiting`);return}}else if(h){_("proxies",`Creating shared HTTP server for ${z.length} domains on port ${M}`,S);let A=await ix(z,w,S);if(await C(M,"0.0.0.0",S)){if(_("proxies",`Port ${M} is already in use, cannot start shared proxy`,S),S)j.warn(`Port ${M} is in use. Shared HTTP proxy cannot start.`);return}if(!xx({routeEntries:A,listenPort:M,sslConfig:null,originGuard:I,verbose:S})){j.error(`Shared HTTP proxy failed to bind :${M}; not exiting`);return}}else for(let A of z)try{let D=A.to||"rpx.localhost";_("proxy",`Starting proxy for ${D} with SSL config: ${!!G}`,A.verbose),await Lx({from:A.from||"localhost:5173",to:D,cleanUrls:A.cleanUrls||!1,https:A.https||!1,cleanup:A.cleanup||!1,vitePluginUsage:A.vitePluginUsage||!1,verbose:A.verbose||!1,_cachedSSLConfig:x._cachedSSLConfig,changeOrigin:A.changeOrigin||!1,loadBalancer:A.loadBalancer,auth:A.auth,path:A.path,pathRewrites:A.pathRewrites})}catch(D){_("proxies",`Failed to start proxy for ${A.to}: ${D}`,A.verbose),j.error(`Failed to start proxy for ${A.to}:`,D)}}async function ix(R,x,S){let w=[],K=new Set;for(let N of R){let z=N.to||"rpx.localhost",$=N.cleanUrls||!1,G=N.path,Y=yx(G),J=jx(N.auth);if(N.redirect){let B=Fx(N.redirect);w.push({host:z,path:G,route:{redirect:B,basePath:Y,auth:J}}),_("proxies",`Route: ${z}${G??""} → redirect ${B.status} ${B.to}${J?" (auth)":""}`,S)}else if(N.static)w.push({host:z,path:G,route:{static:Mx(N.static,$),cleanUrls:$,basePath:Y,auth:J}}),_("proxies",`Route: ${z}${G??""} → static ${typeof N.static==="string"?N.static:N.static.dir}${J?" (auth)":""}`,S);else{let B=u(N.from),X=new URL(B.startsWith("http")?B:`http://${B}`),V=zx(N.from??X.host,N.loadBalancer);Ax(V),Rx.add(V),w.push({host:z,path:G,route:{sourceHost:X.host,upstreamPool:V,cleanUrls:$,changeOrigin:N.changeOrigin||!1,pathRewrites:N.pathRewrites,basePath:Y,auth:J}}),_("proxies",`Route: ${z}${G??""} → ${X.host}${J?" (auth)":""}`,S)}if(K.has(z))continue;if(K.add(z),x&&!Tx(z)&&!z.includes("localhost")&&!z.includes("127.0.0.1"))try{if(!(await L([z],S))[0])await P([z],S)}catch{_("hosts",`Could not add hosts entry for ${z}`,S)}}return w}var l=null;function xR(){if(Q.env.RPX_BIND_HOSTNAME)return Q.env.RPX_BIND_HOSTNAME;if(l)return l;try{l=Object.values(Gx.networkInterfaces()).flat().some((S)=>S&&S.family==="IPv6")?"::":"0.0.0.0"}catch{l="0.0.0.0"}return l}function xx(R){let{routeEntries:x,listenPort:S,sslConfig:w,originGuard:K,verbose:N}=R,z=qx(x),$=Wx((J,B)=>Cx(z,J,B),N),G=K?(J,B)=>K(J)??$(J,B):$,Y=Ex(N);try{let J=Bun.serve({port:S,hostname:xR(),reusePort:nx(),...w?{tls:gx(Array.isArray(w)?w.map((B)=>({serverName:B.serverName,key:B.key,cert:B.cert})):{key:w.key,cert:w.cert,ca:w.ca,requestCert:!1,rejectUnauthorized:!1})}:{},fetch(B,X){return G(B,X)},websocket:Y,error(B){return _("server",`Shared proxy server error: ${B}`,N),new Response(`Server Error: ${B.message}`,{status:500})}});return b.add(J),_("proxies",`Shared ${w?"HTTPS":"HTTP"} proxy listening on port ${S} for ${z.size} domains`,N),J}catch(J){return _("proxies",`Failed to start shared proxy: ${J}`,N),console.error("Failed to start shared proxy:",J),null}}function JR(R){if(R?.vitePluginUsage||!R?.verbose)return;if(console.log(""),console.log(` ${F.green(F.bold("rpx"))} ${F.green(`v${px}`)}`),console.log(` ${F.green("➜")} ${F.dim(R?.from??"")} ${F.dim("➜")} ${F.cyan(R?.ssl?`https://${R?.to}`:`http://${R?.to}`)}`),R?.listenPort!==(R?.ssl?443:80))console.log(` ${F.green("➜")} Listening on port ${R?.listenPort}`);if(R?.cleanUrls)console.log(` ${F.green("➜")} Clean URLs enabled`)}var yS=hx;export{Bx as writeEntry,Q6 as watchRegistry,kR as verifyHttpsChain,CR as trustRootCaForBrowsers,n6 as tearDownDevelopmentDns,d6 as syncDevelopmentDnsFromRegistry,rR as stripBasePath,Nx as stopHealthChecks,v6 as stopDnsServer,w4 as stopDaemon,Lx as startServer,$R as startProxy,hx as startProxies,Ax as startHealthChecks,h6 as startDnsServer,A6 as siteIdForHost,nx as shouldReusePort,a6 as setupResolver,i6 as setupDevelopmentDns,S6 as serverNameFromCertFilename,sR as serveStaticFile,lR as selectUpstream,q as safeStringify,gR as safeRelativePath,bS as safeDeleteFile,g as runViaDaemon,S4 as runDaemon,l6 as resolverFilePath,u6 as resolverBasenamesForDomains,c6 as resolverBasenameForDomain,Mx as resolveStaticRoute,tR as resolveStaticFile,Fx as resolveRedirect,lS as resolvePathRewrite,jx as resolveAuth,H6 as renderStartingPage,I6 as renderFailedPage,y6 as removeStaleRpxHosts,g6 as removeResolver,p6 as removeLegacyTldResolvers,Px as removeHosts,Jx as removeEntry,R4 as releaseDaemonLock,cS as redactSensitive,t6 as reconcileStaleDevelopmentDns,z4 as reconcileDevelopmentDnsOnIdle,j6 as readSiteManifest,J6 as readEntry,e6 as readDaemonPid,DR as readCertSha256Fingerprint,QR as readCertCommonName,V6 as readAll,fx as readAcmeChallenge,TR as pruneStaleRootCas,N6 as projectNameFromHost,u as primaryUpstreamUrl,$6 as portManager,x6 as pathPrefixMatches,HR as parseSha256HashesFromSecurityListing,pR as parseHtpasswd,M6 as parseHostsLine,VR as normalizeSha256Fingerprint,yx as normalizePathPrefix,U6 as normalizeDevDomain,m as matchesWildcard,_6 as matchesAllowedSuffix,Cx as matchHostRoute,R6 as matchHostList,oR as matchHost,bR as markSuccess,aR as markFailure,OR as loadSSLConfig,X6 as listDiscoverableSites,fR as listCertSha256HashesByCommonName,Tx as isWildcardPattern,PS as isValidRootCA,$x as isValidId,vS as isSingleProxyOptions,mS as isSingleProxyConfig,yR as isRootCaTrustedForSsl,qR as isRootCaFingerprintInKeychains,qS as isProcessElevated,C as isPortInUse,B6 as isPidAlive,hS as isMultiProxyOptions,OS as isMultiProxyConfig,w6 as isLikelyHostname,m6 as isDnsServerRunning,o6 as isDaemonRunning,mR as isCertTrusted,wx as httpsConfig,W6 as hostsLineMapsHost,dx as getSudoPassword,LR as getSharedDaemonCertPaths,PR as getRootCAPaths,Yx as getRegistryDir,LS as getPrimaryDomain,ER as getMacosTrustKeychains,WR as getMacosLoginKeychainPath,s6 as getDaemonRpxDir,r6 as getDaemonPidPath,_x as generateCertificate,D6 as gcStaleEntries,hR as forceTrustCertificate,T6 as findStaleRpxHosts,Y6 as findAvailablePort,E6 as filterRpxHostsEntries,uS as extractHostname,z6 as expandHome,US as execSudoSync,k6 as escapeHtml,Vx as ensureDaemonRunning,iR as enforceBasicAuth,f6 as dropStaleRpxHostsLines,cR as devSslToSniEntries,P6 as devDomainsFromHosts,K6 as detectProjectPreset,bx as deriveIdFromTarget,_4 as defaultDaemonSpawnCommand,Sx as defaultConfig,yS as default,_ as debugLog,zx as createUpstreamPool,G6 as createSiteResolver,Ex as createProxyWebSocketHandler,Wx as createProxyFetchHandler,Dx as createOriginGuard,nR as contentTypeFor,b6 as contentLooksLikeRpxResolver,Sx as config,F as colors,vR as clearSslConfigCache,Ix as cleanupCertificates,p as cleanup,L as checkHosts,c as checkExistingCertificates,ZR as certIncludesSanHostnames,Ux as buildSniTlsConfig,uR as buildRegistryTlsProxyOptions,dR as buildRedirectLocation,qx as buildHostRoutes,CS as authorizeSystemAccess,P as addHosts,x4 as acquireDaemonLock,Z6 as SiteSupervisor,UR as SHARED_DEV_HOST_CERT_PATH,MR as RPX_ROOT_CA_COMMON_NAME,O6 as RPX_RESOLVER_MARKER,F6 as RPX_HOSTS_MARKER,cx as OnDemandCertManager,FR as MACOS_SYSTEM_KEYCHAIN,IR as MACOS_CA_TRUST_FLAGS,C6 as LEGACY_TLD_RESOLVER_LABELS,ux as DefaultPortManager,q6 as DNS_STATE_VERSION,L6 as DNS_PORT,eR as ACME_CHALLENGE_PREFIX};
|
|
1
|
+
import{$ as De,$a as Fr,A as _t,Aa as me,B as k,Ba as gr,C as ne,Ca as ge,D as ve,Da as he,E as It,Ea as hr,F as A,Fa as xr,G as ie,Ga as yr,H as Gt,Ha as Pr,I as jt,Ia as Sr,J as zt,Ja as wr,K as ae,Ka as br,L as ce,La as vr,M as Wt,Ma as $r,N as le,Na as Cr,O as Xt,Oa as Rr,P as $e,Pa as Tr,Q as qt,Qa as Or,R as Ce,Ra as F,S as Vt,Sa as Fe,T as Yt,Ta as M,U as Kt,Ua as Dr,V as Jt,Va as Er,W as Zt,Wa as Hr,X as Re,Xa as Ur,Y as Te,Ya as Lr,Z as Qt,Za as kr,_ as Oe,_a as Ar,a as c,aa as I,ab as Mr,b as oe,ba as er,bb as Nr,c as xt,ca as Ee,cb as Br,d as yt,da as tr,db as _r,e as Pt,ea as He,eb as Ir,f as St,fa as rr,g as wt,ga as Ue,gb as Gr,h as bt,ha as Ye,hb as jr,i as vt,ia as or,ib as zr,j as $t,ja as Le,jb as Wr,k as Ct,ka as Ke,kb as Xr,l as Rt,la as sr,lb as qr,m as Tt,ma as nr,mb as Vr,n as Ot,na as ke,nb as Yr,o as Dt,oa as ir,ob as Kr,p as Et,pa as ar,pb as Jr,q as Ht,qa as cr,qb as Zr,r as Ut,ra as lr,rb as Qr,s as Lt,sa as pr,sb as eo,t as kt,ta as ur,tb as to,u as At,ua as dr,ub as ro,v as Ft,va as U,vb as oo,w as Mt,wa as fr,wb as so,x as Nt,xa as Ae,xb as xe,y as Bt,ya as mr,yb as no,z as se,za as fe,zb as io}from"./chunk-z0swphpg.js";import{Ab as be,Bb as qe,Cb as Us,Db as Ls,Eb as ks,Fb as o,Gb as Ve,Hb as As,Ib as H,Jb as Fs,Kb as Ms,Lb as Ns,Mb as Bs,Nb as _s,Ob as Is,Pb as Gs,Qb as js,Rb as zs}from"./chunk-nnnptjfw.js";import{execSync as pt}from"node:child_process";import*as pe from"node:http";import*as Je from"node:net";import*as ue from"node:os";import*as Ze from"node:path";import*as y from"node:process";var V=(t,e)=>(r)=>`\x1B[${t}m${r}\x1B[${e}m`,v={bold:V(1,22),dim:V(2,22),green:V(32,39),cyan:V(36,39)};import*as Ie from"node:fs";import*as Ge from"node:path";import*as T from"node:process";function je(t,e){let s=(e&&e!=="/"?`${t}${e}`:t).replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,128);return s.length>0?s:"rpx"}async function Y(t){if(t.proxies.length===0)throw Error("runViaDaemon: no proxies provided");let e=t.verbose??!1,r=t.registryDir,s=new Set,l=t.proxies.map((u)=>{let h=u.id??je(u.to,u.path);if(!me(h))throw Error(`invalid registry id "${h}" derived from to="${u.to}"`);if(s.has(h))throw Error(`duplicate registry id "${h}" — set an explicit \`id\` on one of the proxies`);return s.add(h),{...u,id:h}}),a=new Date().toISOString();for(let u of l)await ge({id:u.id,from:u.from,to:u.to,path:u.path,pid:t.persistent?void 0:T.pid,cwd:T.cwd(),createdAt:a,cleanUrls:u.cleanUrls,changeOrigin:u.changeOrigin,pathRewrites:u.pathRewrites,static:u.static,loadBalancer:u.loadBalancer},r,e);let n=await xe({rpxDir:t.rpxDir,verbose:e,spawnCommand:t.spawnCommand,startupTimeoutMs:t.startupTimeoutMs,spawnEnv:t.spawnEnv});for(let u of l){let h=u.static?`static ${typeof u.static==="string"?u.static:u.static.dir}`:u.from;c.success(`https://${u.to} → ${h}`)}if(c.info(`(via rpx daemon pid=${n.pid}; \`rpx daemon:status\` to inspect)`),t.detached)return;let f=!1,p=r??fe(),d=l.map((u)=>u.id),g=async()=>{if(f)return;f=!0;for(let u of d)await he(u,r,e).catch((h)=>{o("runner",`removeEntry(${u}) failed: ${h}`,e)})},m=(u)=>{o("runner",`received ${u}, unregistering ${d.length} entries`,e),g().finally(()=>T.exit(0))};T.once("SIGINT",m),T.once("SIGTERM",m),T.once("exit",()=>{if(f)return;for(let u of d)try{Ie.unlinkSync(Ge.join(p,`${u}.json`))}catch{}}),await new Promise(()=>{})}import{spawn as st}from"node:child_process";import*as K from"node:process";class J{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[s,...l]=e.command.split(" "),a=e.cwd||K.cwd();o("start",`Starting process ${t}:`,r),o("start",` Command: ${s} ${l.join(" ")}`,r),o("start",` Working directory: ${a}`,r),o("start",` Environment variables: ${H(e.env)}`,r);let n=st(s,l,{cwd:a,env:{...K.env,...e.env},shell:!0,stdio:"inherit"});return this.processes.set(t,{command:e.command,cwd:a,process:n,env:e.env}),new Promise((f,p)=>{if(n.on("error",(d)=>{if(!this.isShuttingDown)o("start",`Process ${t} failed to start: ${d}`,r),this.processes.delete(t),p(d)}),n.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),p(Error(`Process ${t} exited with code ${d}`))}),r)n.stdout?.on("data",(d)=>{o("process",`[${t}] ${d.toString().trim()}`,!0)}),n.stderr?.on("data",(d)=>{o("process",`[${t}] ERR: ${d.toString().trim()}`,!0)});setTimeout(()=>{if(!this.isShuttingDown&&n.killed)this.processes.delete(t),p(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((s)=>{if(!r.process){s();return}r.process.once("exit",()=>{this.processes.delete(t),o("start",`Process ${t} stopped`,e),s()});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),s()}})}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((s)=>{c.error(`Failed to stop process ${r}:`,s)}));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 Po=new J;import{timingSafeEqual as nt}from"node:crypto";function it(t,e){if(t==null)return!1;let r=Buffer.from(t),s=Buffer.from(e);if(r.length!==s.length)return!1;return nt(r,s)}function Z(t){return t.toLowerCase().replace(/\.$/,"")}var at=["/.well-known/acme-challenge/"];function ct(t){let e=t.headers.get("host");if(e)return Z(e.split(":")[0]);try{return Z(new URL(t.url).hostname)}catch{return""}}function ye(t){let e=t.header.toLowerCase(),r=new Set,s=[];for(let p of t.hosts){let d=Z(p);if(d.startsWith("*."))s.push(d);else r.add(d)}let l=t.exemptPaths??at,a=t.forbiddenMessage??`Forbidden: direct origin access is not allowed; requests must arrive via the CDN.
|
|
2
|
+
`,n=(p)=>{let d=Z(p);return r.has(d)||s.some((g)=>I(d,g))},f=(p)=>{let d=ct(p);if(!n(d))return;let g="/";try{g=new URL(p.url).pathname}catch{}if(l.some((m)=>g.startsWith(m)))return;if(it(p.headers.get(e),t.value))return;return new Response(a,{status:403,headers:{"content-type":"text/plain"}})};return f.protects=n,f}var ze={name:"@stacksjs/rpx",type:"module",version:"0.11.46",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.13"},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 We=ze.version;var ee=new J,ut=new Ae("0.0.0.0"),j=new Set,re=new Set,Pe=!1,Q=null,Se=null;async function W(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)=>{Q=e});try{await ee.stopAll(t?.verbose),c.info("Shutting down proxy servers...");let e=[],r=Array.from(j).map((s)=>new Promise((l)=>{let a=s;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(n){o("cleanup",`Error stopping server: ${n}`,t?.verbose),l()}}));e.push(...r),j.clear();for(let s of re)ce(s);if(re.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 s=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(s)}`,t?.verbose),s.length>0)c.info("Cleaning up hosts file entries..."),e.push(Fe(s,t?.verbose).then(()=>{o("cleanup",`Removed hosts entries for ${s.join(", ")}`,t?.verbose)}).catch((l)=>{o("cleanup",`Failed to remove hosts entries: ${l}`,t?.verbose),c.warn(`Failed to clean up hosts file entries for ${s.join(", ")}:`,l)}))}if(t?.certs&&t.domains?.length){o("cleanup","Cleaning up SSL certificates",t?.verbose),c.info("Cleaning up SSL certificates...");let s=t.domains.map(async(l)=>{try{await ve(l,t?.verbose),o("cleanup",`Removed certificates for ${l}`,t?.verbose)}catch(a){o("cleanup",`Failed to remove certificates for ${l}: ${a}`,t?.verbose),c.warn(`Failed to clean up certificates for ${l}:`,a)}});e.push(...s)}await Promise.allSettled(e),o("cleanup","All cleanup tasks completed successfully",t?.verbose),c.success("All cleanup tasks completed successfully")}catch(e){o("cleanup",`Error during cleanup: ${e}`,t?.verbose),c.error("Error during cleanup:",e)}finally{if(Q)Q();Q=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 we=!1;function Qe(t){if(we){o("signal",`Received second ${t} signal, forcing exit`,!0),y.exit(1);return}we=!0,o("signal",`Received ${t} signal, initiating cleanup`,!0),W().catch((e)=>{o("signal",`Cleanup failed after ${t}: ${e}`,!0),y.exit(1)}).finally(()=>{we=!1})}y.once("SIGINT",()=>Qe("SIGINT"));y.once("SIGTERM",()=>Qe("SIGTERM"));y.on("uncaughtException",(t)=>{c.error("Uncaught exception (continuing):",t)});y.on("unhandledRejection",(t)=>{c.error("Unhandled rejection (continuing):",t)});async function z(t,e,r,s=5){o("connection",`Testing connection to ${t}:${e} (retries left: ${s})`,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 n=()=>new Promise((f,p)=>{let d=Je.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(),p(Error("Connection timed out"))}),d.once("error",(g)=>{o("connection",`Failed to connect to ${t}:${e}: ${g}`,r),d.destroy(),p(g)})});try{await n()}catch(f){let p=f;if(Date.now()-a>l){o("connection",`Connection test timed out after ${l}ms, but continuing anyway`,r),c.warn(`Connection test to ${t}:${e} timed out, but RPX will try to proceed anyway.`);return}if(p.code==="ECONNREFUSED"&&s>0)return o("connection",`Connection refused, server might be starting up. Retrying in 2 seconds... (${s} retries left)`,r),await new Promise((g)=>setTimeout(g,2000)),z(t,e,r,s-1);if(s>0)try{o("connection",`Trying HTTP request to ${t}:${e}`,r),await new Promise((g,m)=>{let u=pe.request({hostname:t,port:e,path:"/",method:"HEAD",timeout:5000},(h)=>{o("connection",`Received HTTP response with status: ${h.statusCode}`,r),g()});u.on("error",(h)=>m(h)),u.on("timeout",()=>{u.destroy(),m(Error("HTTP request timed out"))}),u.end()}),o("connection",`HTTP request to ${t}:${e} succeeded`,r);return}catch(g){return o("connection",`HTTP request to ${t}:${e} failed: ${g}`,r),o("connection",`Retrying socket connection in 2 seconds... (${s} retries left)`,r),await new Promise((m)=>setTimeout(m,2000)),z(t,e,r,s-1)}let d=`Failed to connect to ${t}:${e} after ${5-s} attempts: ${p.message}`;o("connection",`${d}. To bypass this check set RPX_BYPASS_CONNECTION_TEST=true`,r),c.warn(d),c.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 Me(t){o("server",`Starting server with options: ${H(t)}`,t.verbose);let e=A(t.from),r=new URL(e.startsWith("http")?e:`http://${e}`),s=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=[s.hostname];if(Ne(t)&&!s.hostname.includes("localhost")&&!s.hostname.includes("127.0.0.1")){o("hosts",`Checking if hosts file entry exists for: ${s.hostname}`,t?.verbose);try{if(!(await M(a,t.verbose))[0]){c.info(`Adding ${s.hostname} to hosts file...`),c.info("This may require sudo/administrator privileges");try{await F(a,t.verbose)}catch(p){if(c.error("Failed to add hosts entry:",p.message),c.warn("You can manually add this entry to your hosts file:"),c.warn(`127.0.0.1 ${s.hostname}`),c.warn(`::1 ${s.hostname}`),y.platform==="win32")c.warn("On Windows:"),c.warn("1. Run notepad as administrator"),c.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");else c.warn("On Unix systems:"),c.warn("sudo nano /etc/hosts")}}else o("hosts",`Host entry already exists for ${s.hostname}`,t.verbose)}catch(f){c.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),c.error(f.message),c.warn("Continuing with proxy setup despite connection test failure..."),c.info("If you need to bypass connection testing, set environment variable RPX_BYPASS_CONNECTION_TEST=true")}let n=t._cachedSSLConfig||null;if(t.https)try{if(t.https===!0)t.https=ne({...t,to:s.hostname});if(n=await k({...t,to:s.hostname,https:t.https}),!n){if(o("ssl",`Generating new certificates for ${s.hostname}`,t.verbose),await se({...t,from:r.toString(),to:s.hostname,https:t.https}),n=await k({...t,to:s.hostname,https:t.https}),!n)throw Error(`Failed to load SSL configuration after generating certificates for ${s.hostname}`)}}catch(f){throw o("server",`SSL setup failed: ${f}`,t.verbose),f}o("server",`Setting up reverse proxy with SSL config for ${s.hostname}`,t.verbose),await ft({...t,from:e,originalFrom:t.from||e,to:s.hostname,fromPort:l,sourceUrl:{hostname:r.hostname,host:r.host},ssl:n})}async function dt(t,e,r,s,l,a,n,f,p,d,g,m){o("proxy",`Creating proxy server ${t} -> ${e} with cleanUrls: ${f}`,n);let u=ie(g??s.host,m);ae(u);let h=[{host:e,route:{sourceHost:s.host,upstreamPool:u,cleanUrls:f||!1,changeOrigin:p||!1,basePath:"/",auth:d}}];if(!te({routeEntries:h,listenPort:r,sslConfig:l,originGuard:null,verbose:n??!1}))throw ce(u),Error(`Failed to start proxy server for ${e} on port ${r}`);re.add(u),ht({from:t,to:e,vitePluginUsage:a,listenPort:r,ssl:!!l,cleanUrls:f,verbose:n})}async function ft(t){o("setup",`Setting up reverse proxy: ${H(t)}`,t.verbose);let{from:e,originalFrom:r,to:s,sourceUrl:l,ssl:a,verbose:n,cleanup:f,vitePluginUsage:p,changeOrigin:d,cleanUrls:g}=t,m=80,u=443,h=tt(),O=t.portManager||ut,N=Ne(t);try{if(N&&s&&!s.includes("localhost")&&!s.includes("127.0.0.1")){if(!(await M([s],n))[0]){c.warn(`The hostname ${s} isn't in your hosts file. Adding it now...`);try{await F([s],n),c.success(`Added ${s} to your hosts file.`)}catch(B){c.error(`Failed to add ${s} to your hosts file: ${B}`),c.info(`You may need to manually add '127.0.0.1 ${s}' to your /etc/hosts file.`)}}}else if(N&&y.platform!=="darwin"&&s&&s.includes("localhost")&&!s.match(/^(localhost|127\.0\.0\.1)$/)){if(!(await M([s],n))[0]){o("hosts",`${s} not found in hosts file, adding...`,n);try{await F([s],n)}catch(B){o("hosts",`Failed to add ${s} to hosts file: ${B}`,n)}}}if(a&&!O.usedPorts.has(m)){if(!await U(m,h,n))o("setup","Starting HTTP redirect server",n),et(n),O.usedPorts.add(m);else if(o("setup","Port 80 is in use, skipping HTTP redirect",n),n)c.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available")}let P=a?u:m,C=await U(P,h,n),b;if(C){if(o("setup",`Port ${P} is already in use`,n),n)c.warn(`Port ${P} is already in use. This may be another instance of rpx or another service.`);if(P===443){if(b=await O.getNextAvailablePort(3443,!0),o("setup",`Using port ${b} instead of ${P}`,n),n)c.info(`Using port ${b} instead. Access your site at https://${s}:${b}`)}else if(b=await O.getNextAvailablePort(P+1000,!0),o("setup",`Using port ${b} instead of ${P}`,n),n)c.info(`Using port ${b} instead. Access your site at http://${s}:${b}`)}else b=P,O.usedPorts.add(b),o("setup",`Using standard ${P===443?"HTTPS":"HTTP"} port ${P} for ${s}`,n);await dt(e,s,b,l,a,p,n,g,d,le(t.auth),r,t.loadBalancer)}catch(P){o("setup",`Setup failed: ${P}`,n),c.error(`Failed to setup reverse proxy: ${P.message}`),W({domains:[s],hosts:typeof f==="boolean"?f:f?.hosts,certs:typeof f==="boolean"?f:f?.certs,verbose:n,vitePluginUsage:p})}}function et(t,e=80,r=443,s,l,a){o("redirect",`Starting HTTP redirect server on port ${e}`,t);let n=pe.createServer((f,p)=>{let d=f.url?f.url.split("?",1)[0]:"";if(d.startsWith("/.well-known/acme-challenge/")){if(l){let h=l.challengeStore.handlePath(d);if(h!==void 0){o("redirect",`Serving on-demand ACME challenge ${d}`,t),p.writeHead(200,{"content-type":"text/plain"}),p.end(h);return}}if(s){let h=Oe(s,d);if(h!=null){o("redirect",`Serving ACME challenge ${d}`,t),p.writeHead(200,{"content-type":"text/plain"}),p.end(h);return}}if(l){p.writeHead(404,{"content-type":"text/plain"}),p.end("challenge not found");return}}let g=f.headers.host||"",m=g.includes(":")?g.slice(0,g.indexOf(":")):g;if(l&&m&&!l.hasCert(m)&&(!a||a(m)))l.ensureCert(m).catch(()=>{});let u=r===443?m:`${m}:${r}`;o("redirect",`Redirecting request from ${g}${f.url} to https://${u}`,t),p.writeHead(301,{Location:`https://${u}${f.url}`}),p.end()}).listen(e);j.add(n),o("redirect","HTTP redirect server started",t)}function mt(t){let e={...oe,...t};if(o("proxy",`Starting proxy with options: ${H(e)}`,e?.verbose),e.viaDaemon){if(!e.from||!e.to){c.error("viaDaemon mode requires both `from` and `to`");return}Y({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((p)=>{c.error(`Failed to register with rpx daemon: ${p.message}`),y.exit(1)});return}let r=e.to||"",s=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"],n=["test","localhost","local","example","invalid"];if(l&&a.includes(s)&&e?.verbose)c.warn(`The .${s} TLD may not work reliably for local development`),c.info(` Google owns .${s} with HSTS preloading, which can bypass local DNS`),c.info(" Consider using a reserved TLD: .test, .localhost, or .local");if(l)import("./chunk-086aa2f7.js").then(({setupDevelopmentDns:p})=>{p({domains:[r],verbose:e.verbose}).then((d)=>{if(d)Promise.resolve().then(()=>{if(e.verbose)if(n.includes(s))c.success(`DNS server started for .${s} domains`);else c.success(`DNS server started for .${s} domains (hosts file entry also added)`)});else o("dns",`Could not start DNS server - ${r} may not resolve in browser`,e.verbose)})}).catch((p)=>{o("dns",`Failed to start DNS server: ${p}`,e.verbose)});let f={from:e.from,to:e.to,cleanUrls:e.cleanUrls,https:ne(e),cleanup:e.cleanup,vitePluginUsage:e.vitePluginUsage,changeOrigin:e.changeOrigin,verbose:e.verbose,regenerateUntrustedCerts:e.regenerateUntrustedCerts};o("proxy",`Server options: ${H(f)}`,e.verbose),Me(f).catch((p)=>{o("proxy",`Failed to start proxy: ${p}`,e.verbose),c.error(`Failed to start proxy: ${p.message}`),W({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 gt(t){return t?.verbose||!1}function Ne(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 Be(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=gt(e),s=Ne(e);if(o("config",`Starting with config: ${H(e,2)}`,r),o("config",`Is multi-proxy? ${"proxies"in e}`,r),o("config",`Hosts management enabled? ${s}`,r),e.viaDaemon){let x="proxies"in e&&Array.isArray(e.proxies)?e.proxies.map((S)=>({id:S.id,from:S.from,to:S.to,path:S.path,cleanUrls:S.cleanUrls??e.cleanUrls,changeOrigin:S.changeOrigin??e.changeOrigin,pathRewrites:S.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 Y({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),c.info(`Starting command for ${x}...`),await ee.startProcess(x,i.start,r);let S=A(i.from),w=new URL(S.startsWith("http")?S:`http://${S}`),R=w.hostname||"localhost",D=Number(w.port)||80;try{await z(R,D,r),o("watch",`Dev server is ready at ${R}:${D}`,r)}catch(de){o("watch",`Connection check failed, but continuing with proxy setup: ${de}`,r),c.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(S){throw o("watch",`Failed to start command for ${x}: ${S}`,r),Error(`Failed to start command for ${x}: ${S}`)}}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 ee.startProcess(i,e.start,r);let x=A(e.from),S=new URL(x.startsWith("http")?x:`http://${x}`),w=S.hostname||"localhost",R=Number(S.port)||80;try{await z(w,R,r),o("watch",`Dev server is ready at ${w}:${R}`,r)}catch(D){o("watch",`Connection check failed, but continuing with proxy setup: ${D}`,r),c.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 l="proxies"in e&&Array.isArray(e.proxies)?e.proxies[0]?.to:("to"in e)?e.to:"rpx.localhost";if(y.platform!=="win32"&&(e.https||s)){if(!qe())try{o("sudo","Pre-acquiring sudo credentials for privileged operations",r),pt("sudo -v",{stdio:"inherit"})}catch{o("sudo","Could not pre-acquire sudo credentials",r)}}let a=[];if(e.productionCerts){if(a=await Le(e.productionCerts,r),a.length>0)o("ssl",`Using ${a.length} production SNI cert(s): ${a.map((i)=>i.serverName).join(", ")}`,r)}if(e.https){let i=a.length>0?null:await k(e);if(!i&&a.length===0){if(o("ssl",`No valid or trusted certificates found for ${l}, generating new ones`,e.verbose),await se(e),i=await k(e),!i)throw Error(`Failed to load SSL certificates after generation for ${l}`)}else o("ssl",`Using existing and trusted certificates for ${l}`,e.verbose);e._cachedSSLConfig=i}let n="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}],f=n.map((i)=>i.to||"rpx.localhost"),p=a.length>0?a:e._cachedSSLConfig??null,d=f.filter((i)=>i&&!i.includes("localhost")&&!i.includes("127.0.0.1")),g=["dev","app","page","new","day","foo"],m=["test","localhost","local","example","invalid"],u=[...new Set(d.map((i)=>i.split(".").pop()?.toLowerCase()))],h=u.filter((i)=>!!i&&g.includes(i));if(h.length>0&&r)c.warn(`The following TLDs may not work reliably for local development: ${h.map((i)=>`.${i}`).join(", ")}`),c.info(" These TLDs have HSTS preloading which can bypass local DNS"),c.info(" Consider using reserved TLDs: .test, .localhost, or .local");if(s&&y.platform==="darwin"&&d.length>0){let{setupDevelopmentDns:i}=await import("./chunk-086aa2f7.js");if(await i({domains:d,verbose:r})){if(r)if(u.every((w)=>!!w&&m.includes(w)))c.success(`DNS server started for ${u.map((w)=>`.${w}`).join(", ")} domains`);else c.success(`DNS server started for ${u.map((w)=>`.${w}`).join(", ")} domains (hosts file entries also added)`)}else o("dns","Could not start DNS server - custom domains may not resolve",r)}let O=async()=>{o("cleanup","Starting cleanup handler",e.verbose);try{let{tearDownDevelopmentDns:i}=await import("./chunk-086aa2f7.js");await i({verbose:e.verbose})}catch(i){o("cleanup",`Error stopping DNS server: ${i}`,e.verbose)}try{await ee.stopAll(e.verbose)}catch(i){o("cleanup",`Error stopping processes: ${i}`,e.verbose)}await W({domains:f,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",O),y.on("SIGTERM",O);let N=e.singlePortMode===!0,P=e.httpsPort??443,C=e.httpPort??80,b=e.originGuard?ye(e.originGuard):null,X=!!p&&(n.length>1||N||a.length>0),B=!p&&N&&n.length>0;if(X&&p){o("proxies",`Creating shared HTTPS server for ${n.length} domains on port ${P}`,r);let i=await Xe(n,s,r),x=null,S=e.onDemandTls,w=S?.enabled?new ke({config:S,certsDir:S.certsDir??e.productionCerts?.certsDir??Ze.join(ue.homedir(),".stacks","rpx","on-demand-certs"),initial:a,verbose:r,onCertAdded:(L)=>{if(Ke()==="restart")o("on-demand","certificate installed; restarting supervised gateway to reload TLS",r),setTimeout(()=>y.kill(y.pid,"SIGTERM"),10).unref();else de(L)}}):null,R=null,D=!1;async function de(L){if(R=L,D)return;D=!0;try{while(R){let q=R;if(R=null,o("proxies",`rebuilding :${P} with ${q.length} SNI cert(s)`,r),x)j.delete(x),x.stop(!1);let E=!1;for(let _=0;!E&&_<60;_++){let _e=te({routeEntries:i,listenPort:P,sslConfig:q,originGuard:b,verbose:r});if(_e){x=_e,E=!0;break}await new Promise((ot)=>setTimeout(ot,Math.min(25*2**Math.min(_,4),500)))}if(!E)c.error(`rpx: CRITICAL — could not rebind :${P} after cert issuance; HTTPS unbound until the next cert event or a gateway restart`)}}finally{D=!1}}if(!await U(C,"0.0.0.0",r)){let L=new Set(i.map((E)=>E.host)),q=(E)=>L.has(E)||[...L].some((_)=>I(E,_));et(r,C,P,e.acmeChallengeWebroot,w,q)}if(await U(P,"0.0.0.0",r)){if(o("proxies",`Port ${P} is already in use, cannot start shared proxy`,r),r)c.warn(`Port ${P} is in use. Shared HTTPS proxy cannot start.`);return}let rt=w&&w.sniEntries().length>0?w.sniEntries():p;if(x=te({routeEntries:i,listenPort:P,sslConfig:rt,originGuard:b,verbose:r}),!x){c.error(`Shared HTTPS proxy failed to bind :${P}; not exiting`);return}}else if(B){o("proxies",`Creating shared HTTP server for ${n.length} domains on port ${C}`,r);let i=await Xe(n,s,r);if(await U(C,"0.0.0.0",r)){if(o("proxies",`Port ${C} is already in use, cannot start shared proxy`,r),r)c.warn(`Port ${C} is in use. Shared HTTP proxy cannot start.`);return}if(!te({routeEntries:i,listenPort:C,sslConfig:null,originGuard:b,verbose:r})){c.error(`Shared HTTP proxy failed to bind :${C}; not exiting`);return}}else for(let i of n)try{let x=i.to||"rpx.localhost";o("proxy",`Starting proxy for ${x} with SSL config: ${!!p}`,i.verbose),await Me({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})}catch(x){o("proxies",`Failed to start proxy for ${i.to}: ${x}`,i.verbose),c.error(`Failed to start proxy for ${i.to}:`,x)}}async function Xe(t,e,r){let s=[],l=new Set;for(let a of t){let n=a.to||"rpx.localhost",f=a.cleanUrls||!1,p=a.path,d=Ee(p),g=le(a.auth);if(a.redirect){let m=$e(a.redirect);s.push({host:n,path:p,route:{redirect:m,basePath:d,auth:g}}),o("proxies",`Route: ${n}${p??""} → redirect ${m.status} ${m.to}${g?" (auth)":""}`,r)}else if(a.static)s.push({host:n,path:p,route:{static:Ce(a.static,f),cleanUrls:f,basePath:d,auth:g}}),o("proxies",`Route: ${n}${p??""} → static ${typeof a.static==="string"?a.static:a.static.dir}${g?" (auth)":""}`,r);else{let m=A(a.from),u=new URL(m.startsWith("http")?m:`http://${m}`),h=ie(a.from??u.host,a.loadBalancer);ae(h),re.add(h),s.push({host:n,path:p,route:{sourceHost:u.host,upstreamPool:h,cleanUrls:f,changeOrigin:a.changeOrigin||!1,pathRewrites:a.pathRewrites,basePath:d,auth:g}}),o("proxies",`Route: ${n}${p??""} → ${u.host}${g?" (auth)":""}`,r)}if(l.has(n))continue;if(l.add(n),e&&!De(n)&&!n.includes("localhost")&&!n.includes("127.0.0.1"))try{if(!(await M([n],r))[0])await F([n],r)}catch{o("hosts",`Could not add hosts entry for ${n}`,r)}}return s}var G=null;function tt(){if(y.env.RPX_BIND_HOSTNAME)return y.env.RPX_BIND_HOSTNAME;if(G)return G;try{G=Object.values(ue.networkInterfaces()).flat().some((r)=>r&&r.family==="IPv6")?"::":"0.0.0.0"}catch{G="0.0.0.0"}return G}function te(t){let{routeEntries:e,listenPort:r,sslConfig:s,originGuard:l,verbose:a}=t,n=He(e),f=Re((g,m)=>Ue(n,g,m),a),p=l?(g,m)=>l(g)??f(g,m):f,d=Te(a);try{let g=Bun.serve({port:r,hostname:tt(),reusePort:Ve(),...s?{tls:Ye(Array.isArray(s)?s.map((m)=>({serverName:m.serverName,key:m.key,cert:m.cert})):{key:s.key,cert:s.cert,ca:s.ca,requestCert:!1,rejectUnauthorized:!1})}:{},fetch(m,u){return p(m,u)},websocket:d,error(m){return o("server",`Shared proxy server error: ${m}`,a),new Response(`Server Error: ${m.message}`,{status:500})}});return j.add(g),o("proxies",`Shared ${s?"HTTPS":"HTTP"} proxy listening on port ${r} for ${n.size} domains`,a),g}catch(g){return o("proxies",`Failed to start shared proxy: ${g}`,a),console.error("Failed to start shared proxy:",g),null}}function ht(t){if(t?.vitePluginUsage||!t?.verbose)return;if(console.log(""),console.log(` ${v.green(v.bold("rpx"))} ${v.green(`v${We}`)}`),console.log(` ${v.green("➜")} ${v.dim(t?.from??"")} ${v.dim("➜")} ${v.cyan(t?.ssl?`https://${t?.to}`:`http://${t?.to}`)}`),t?.listenPort!==(t?.ssl?443:80))console.log(` ${v.green("➜")} Listening on port ${t?.listenPort}`);if(t?.cleanUrls)console.log(` ${v.green("➜")} Clean URLs enabled`)}var Hs=Be;export{Qt as ACME_CHALLENGE_PREFIX,Mr as DNS_PORT,Hr as DNS_STATE_VERSION,Ae as DefaultPortManager,Ur as LEGACY_TLD_RESOLVER_LABELS,vt as MACOS_CA_TRUST_FLAGS,$t as MACOS_SYSTEM_KEYCHAIN,ke as OnDemandCertManager,$r as RPX_HOSTS_MARKER,Nr as RPX_RESOLVER_MARKER,Ct as RPX_ROOT_CA_COMMON_NAME,Lt as SHARED_DEV_HOST_CERT_PATH,Sr as SiteSupervisor,to as acquireDaemonLock,F as addHosts,Ls as authorizeSystemAccess,He as buildHostRoutes,qt as buildRedirectLocation,At as buildRegistryTlsProxyOptions,Le as buildSniTlsConfig,St as certIncludesSanHostnames,k as checkExistingCertificates,M as checkHosts,W as cleanup,ve as cleanupCertificates,_t as clearSslConfigCache,v as colors,oe as config,jr as contentLooksLikeRpxResolver,Vt as contentTypeFor,ye as createOriginGuard,Re as createProxyFetchHandler,Te as createProxyWebSocketHandler,ur as createSiteResolver,ie as createUpstreamPool,o as debugLog,Hs as default,oe as defaultConfig,so as defaultDaemonSpawnCommand,je as deriveIdFromTarget,pr as detectProjectPreset,Fr as devDomainsFromHosts,kt as devSslToSniEntries,Or as dropStaleRpxHostsLines,Xt as enforceBasicAuth,xe as ensureDaemonRunning,wr as escapeHtml,ks as execSudoSync,ir as expandHome,Fs as extractHostname,Tr as filterRpxHostsEntries,fr as findAvailablePort,Dr as findStaleRpxHosts,Bt as forceTrustCertificate,yr as gcStaleEntries,se as generateCertificate,Zr as getDaemonPidPath,Jr as getDaemonRpxDir,Rt as getMacosLoginKeychainPath,Tt as getMacosTrustKeychains,Ns as getPrimaryDomain,fe as getRegistryDir,Ft as getRootCAPaths,Mt as getSharedDaemonCertPaths,qe as getSudoPassword,Rr as hostsLineMapsHost,ne as httpsConfig,It as isCertTrusted,eo as isDaemonRunning,Ir as isDnsServerRunning,nr as isLikelyHostname,Bs as isMultiProxyConfig,_s as isMultiProxyOptions,gr as isPidAlive,U as isPortInUse,Us as isProcessElevated,Ht as isRootCaFingerprintInKeychains,Et as isRootCaTrustedForSsl,Gs as isSingleProxyConfig,Is as isSingleProxyOptions,me as isValidId,Ms as isValidRootCA,De as isWildcardPattern,Ot as listCertSha256HashesByCommonName,dr as listDiscoverableSites,Nt as loadSSLConfig,zt as markFailure,jt as markSuccess,er as matchHost,rr as matchHostList,Ue as matchHostRoute,sr as matchesAllowedSuffix,I as matchesWildcard,Lr as normalizeDevDomain,Ee as normalizePathPrefix,xt as normalizeSha256Fingerprint,Cr as parseHostsLine,Wt as parseHtpasswd,bt as parseSha256HashesFromSecurityListing,tr as pathPrefixMatches,mr as portManager,A as primaryUpstreamUrl,cr as projectNameFromHost,Dt as pruneStaleRootCas,Oe as readAcmeChallenge,xr as readAll,Pt as readCertCommonName,yt as readCertSha256Fingerprint,Qr as readDaemonPid,hr as readEntry,lr as readSiteManifest,io as reconcileDevelopmentDnsOnIdle,Kr as reconcileStaleDevelopmentDns,As as redactSensitive,ro as releaseDaemonLock,he as removeEntry,Fe as removeHosts,Wr as removeLegacyTldResolvers,Yr as removeResolver,Er as removeStaleRpxHosts,vr as renderFailedPage,br as renderStartingPage,le as resolveAuth,js as resolvePathRewrite,$e as resolveRedirect,Kt as resolveStaticFile,Ce as resolveStaticRoute,kr as resolverBasenameForDomain,Ar as resolverBasenamesForDomains,Gr as resolverFilePath,oo as runDaemon,Y as runViaDaemon,zs as safeDeleteFile,Yt as safeRelativePath,H as safeStringify,Gt as selectUpstream,Jt as serveStaticFile,or as serverNameFromCertFilename,Xr as setupDevelopmentDns,zr as setupResolver,Ve as shouldReusePort,ar as siteIdForHost,Br as startDnsServer,ae as startHealthChecks,Be as startProxies,mt as startProxy,Me as startServer,no as stopDaemon,_r as stopDnsServer,ce as stopHealthChecks,Zt as stripBasePath,qr as syncDevelopmentDnsFromRegistry,Vr as tearDownDevelopmentDns,Ut as trustRootCaForBrowsers,wt as verifyHttpsChain,Pr as watchRegistry,ge as writeEntry};
|
package/dist/proxy-handler.d.ts
CHANGED
|
@@ -11,6 +11,16 @@ import type { UpstreamPool } from './load-balancer';
|
|
|
11
11
|
* read once per handler so tests (and reloads) can pick up a new value.
|
|
12
12
|
*/
|
|
13
13
|
export declare function wsPendingCapBytes(): number;
|
|
14
|
+
/**
|
|
15
|
+
* Compress an upstream text response at the proxy boundary when the app did
|
|
16
|
+
* not. This is deliberately streaming: buffering a large server-rendered page
|
|
17
|
+
* would trade transfer time for a later first byte, while CompressionStream
|
|
18
|
+
* can start sending gzip output as soon as the upstream produces body chunks.
|
|
19
|
+
*
|
|
20
|
+
* Applications that already serve Brotli/gzip pass through untouched. Range
|
|
21
|
+
* responses and `no-transform` opt-outs are also preserved byte-for-byte.
|
|
22
|
+
*/
|
|
23
|
+
export declare function compressResponseForClient(request: Request, response: Response): Response;
|
|
14
24
|
/**
|
|
15
25
|
* Strip the route's mount prefix (`basePath`) from a request pathname so a
|
|
16
26
|
* target mounted under `/docs` sees `/` for `/docs` and `/guide` for
|
|
@@ -45,9 +55,10 @@ export declare interface ProxyRoute {
|
|
|
45
55
|
stripBasePathPrefix?: boolean
|
|
46
56
|
auth?: ResolvedAuth
|
|
47
57
|
}
|
|
48
|
-
/** Minimal shape of the Bun server needed for WebSocket upgrades. */
|
|
58
|
+
/** Minimal shape of the Bun server needed for WebSocket upgrades and peer lookup. */
|
|
49
59
|
export declare interface ProxyServer {
|
|
50
60
|
upgrade: (req: Request, options?: { data?: unknown, headers?: Bun.HeadersInit }) => boolean
|
|
61
|
+
requestIP?: (req: Request) => { address: string } | null
|
|
51
62
|
}
|
|
52
63
|
/*` → app, `/docs*` → static dir). Callers
|
|
53
64
|
* that only route by host can ignore the second argument — it's optional so
|
package/dist/proxy-pool.d.ts
CHANGED
package/package.json
CHANGED
package/dist/chunk-7sytrykf.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{ab as a,bb as b,cb as c,db as d,eb as e,fb as f,gb as g,hb as h,ib as i,jb as j,kb as k,lb as l,mb as m,nb as n,ob as o}from"./chunk-mbba4csc.js";import"./chunk-w4t1nar9.js";export{m as tearDownDevelopmentDns,l as syncDevelopmentDnsFromRegistry,d as stopDnsServer,c as startDnsServer,i as setupResolver,k as setupDevelopmentDns,g as resolverFilePath,n as removeResolver,j as removeLegacyTldResolvers,o as reconcileStaleDevelopmentDns,f as isRpxDevelopmentDnsAnswering,e as isDnsServerRunning,h as contentLooksLikeRpxResolver,b as RPX_RESOLVER_MARKER,a as DNS_PORT};
|
package/dist/chunk-bvq3wmhm.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{Bb as a,Cb as b,Db as c,Eb as d,Fb as e,Gb as f,Hb as g,Ib as h,Jb as i,Kb as j,Lb as k,Mb as l,Nb as m,Ob as n,Pb as o,Qb as p,Rb as q}from"./chunk-w4t1nar9.js";export{f as shouldReusePort,h as safeStringify,q as safeDeleteFile,p as resolvePathRewrite,g as redactSensitive,j as isValidRootCA,n as isSingleProxyOptions,o as isSingleProxyConfig,b as isProcessElevated,m as isMultiProxyOptions,l as isMultiProxyConfig,a as getSudoPassword,k as getPrimaryDomain,i as extractHostname,d as execSudoSync,e as debugLog,c as authorizeSystemAccess};
|