web-listener 0.20.0 → 0.20.1

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/index.d.ts CHANGED
@@ -1375,4 +1375,4 @@ declare class Property<T> {
1375
1375
  declare const makeMemo: <T, Args extends any[] = []>(fn: (req: IncomingMessage, ...args: Args) => T, ...args: Args) => ((req: IncomingMessage) => T);
1376
1376
 
1377
1377
  export { BlockingQueue, CONTINUE, FileFinder, HTTPError, LoadOnDemand, NEXT_ROUTE, NEXT_ROUTER, Negotiator, PP_BASE_DENY_2026, Property, Queue, Router, STOP, ServerSentEvents, WebListener, WebSocketError, WebSocketMessage, WebSocketMessages, acceptBody, acceptUpgrade, addTeardown, anyHandler, checkIfModified, checkIfRange, compareETag, compressFileOffline, compressFilesInDir, conditionalErrorHandler, decompressMime, defer, delegateUpgrade, emitError, errorHandler, fileServer, findCause, generateStrongETag, generateWeakETag, getAbortSignal, getAbsolutePath, getAddressURL, getAuthScopes, getAuthorization, getBodyJSON, getBodyStream, getBodyText, getBodyTextStream, getCharset, getFormData, getFormFields, getIfRange, getMime, getPathParameter, getPathParameters, getQuery, getRange, getRemainingPathComponents, getSearch, getSearchParams, getTextDecoder, getTextDecoderStream, getWebSocketOrigin, hasAuthScope, isSoftClosed, isWebSocketRequest, jsonErrorHandler, loadOnDemand, makeAcceptWebSocket, makeAddressTester, makeGetClient, makeMemo, makeTempFileStorage, makeWebSocketFallbackTokenFetcher, mergePermissionsPolicy, negotiateEncoding, nextWebSocketMessage, parseAddress, proxy, readHTTPDateSeconds, readHTTPInteger, readHTTPKeyValues, readHTTPQualityValues, readHTTPUnquotedCommaSeparated, readMimeTypes, registerCharset, registerMime, registerUTF32, removeForwarded, replaceForwarded, requestHandler, requireAuthScope, requireBearerAuth, resetMime, restoreAbsolutePath, sanitiseAndAppendForwarded, scheduleClose, sendCSVStream, sendFile, sendJSON, sendJSONStream, sendRanges, setDefaultCacheHeaders, setSoftCloseHandler, simpleAppendForwarded, simplifyRange, staticContent, staticJSON, stringPredicate, toListeners, typedErrorHandler, upgradeHandler, willSendBody };
1378
- export type { AcceptUpgradeHandler, AcceptUpgradeResult, Address, AugmentedFormData, AugmentedServer, CSVOptions, CombinedServerOptions, CommonMethod, CommonUpgrade, CompressionInfo, CompressionOptions, ErrorHandler, FileFinderCore, FileFinderOptions, FileNegotiation, FileNegotiationOption, FileServerOptions, FormField, GetClient, GetClientOptions, GetFormDataOptions, GetFormFieldsOptions, GetRangeOptions, HTTPErrorOptions, HTTPRange, HandlerResult, JSONErrorHandlerOptions, JSONOptions, ListenOptions, ListenerOptions, NativeListeners, NegotiationOutput, NegotiationOutputHeaders, ParametersFromPath, PostCheckFile, PostCheckFileInfo, PreCheckFile, PreCheckFileInfo, ProxyChain, ProxyNode, ProxyOptions, ProxyRequestHeaderAdapter, ProxyResponseHeaderAdapter, QualityValue, RangePart, RequestHandler, ResolvedFileInfo, ServerErrorCallback, ServerGeneralErrorCallback, ServerSentEvent, SimplifyRangeOptions, StaticContentOptions, TokenAuthHandler, UpgradeErrorHandler, UpgradeHandler, UpgradeListener, WithPathParameters };
1378
+ export type { AcceptUpgradeHandler, AcceptUpgradeResult, Address, AugmentedFormData, AugmentedServer, CSVOptions, CombinedServerOptions, CommonMethod, CommonUpgrade, CompressionInfo, CompressionOptions, ErrorHandler, FallbackOptions, FileFinderCore, FileFinderOptions, FileNegotiation, FileNegotiationOption, FileServerOptions, FormField, GetClient, GetClientOptions, GetFormDataOptions, GetFormFieldsOptions, GetRangeOptions, HTTPErrorOptions, HTTPRange, HandlerResult, JSONErrorHandlerOptions, JSONOptions, ListenOptions, ListenerOptions, NativeListeners, NegotiationOutput, NegotiationOutputHeaders, ParametersFromPath, PostCheckFile, PostCheckFileInfo, PreCheckFile, PreCheckFileInfo, ProxyChain, ProxyNode, ProxyOptions, ProxyRequestHeaderAdapter, ProxyResponseHeaderAdapter, QualityValue, RangePart, RequestHandler, ResolvedFileInfo, ServerErrorCallback, ServerGeneralErrorCallback, ServerSentEvent, SimplifyRangeOptions, StaticContentOptions, TokenAuthHandler, UpgradeErrorHandler, UpgradeHandler, UpgradeListener, WithPathParameters };
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-listener",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
4
4
  "description": "a small server abstraction for creating API and resource endpoints with middleware",
5
5
  "author": "David Evans",
6
6
  "license": "MIT",
package/run.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env -S node --disable-proto=delete --disallow-code-generation-from-strings --force-node-api-uncaught-exceptions-policy --no-addons
2
- import{readFile as e}from"node:fs/promises";import{join as t,dirname as o,resolve as n,basename as r}from"node:path";import{spawn as s,spawnSync as i}from"node:child_process";import{createServer as c}from"node:http";import{Router as a,requestHandler as f,addTeardown as p,CONTINUE as l,proxy as u,Negotiator as h,fileServer as d,generateWeakETag as w,getSearch as g,getQuery as m,getPathParameter as y,WebListener as $,findCause as b,HTTPError as v,stringPredicate as x,compressFilesInDir as S,readMimeTypes as k,decompressMime as O,resetMime as j,registerMime as E}from"./index.js";const z=new Map([["","dir"],["-a","host"],["-b","brotli"],["-c","config-file"],["-C","config-json"],["-d","dir"],["-e","ext"],["-g","gzip"],["-h","help"],["-H","header"],["-p","port"],["-P","proxy"],["-v","version"],["br","brotli"],["gz","gzip"],["zst","zstd"]]),A=new Map([["config-file",{type:"string"}],["config-json",{type:"string"}],["dir",{type:"string"}],["exec",{type:"string",multi:!0}],["ext",{type:"string",multi:!0}],["port",{type:"number"}],["host",{type:"string"}],["zstd",{type:"boolean"}],["brotli",{type:"boolean"}],["gzip",{type:"boolean"}],["deflate",{type:"boolean"}],["proxy",{type:"string"}],["404",{type:"string"}],["spa",{type:"string"}],["header",{type:"string",multi:!0}],["mime",{type:"string",multi:!0}],["mime-types",{type:"string",multi:!0}],["write-compressed",{type:"boolean"}],["min-compress",{type:"number"}],["no-serve",{type:"boolean"}],["log",{type:"string"}],["help",{type:"boolean"}],["version",{type:"boolean"}]]);function N(e,t){const o=new Map(Object.entries(e??{}).map(([e,t])=>[e,Array.isArray(t)?[...t]:[t]]));for(const[e,n=""]of t){const t=o.get(e);t?t.push(n):o.set(e,[n])}return Object.fromEntries(o.entries())}const R=new Map([["zstd",{value:"zstd",file:"{file}.zst"}],["brotli",{value:"br",file:"{file}.br"}],["gzip",{value:"gzip",file:"{file}.gz"}],["deflate",{value:"deflate",file:"{file}.deflate"}]]);const M=e=>(t,o)=>{if(t!==e)throw new G(`expected ${JSON.stringify(e)}`,o,8);return e},J=e=>(t,o)=>{if(!e.has(t))throw new G(`expected one of ${JSON.stringify(e)}`,o,7);return t},T=e=>(t,o)=>{const n=[];let r=9;for(const s of e)try{return s(t,o)}catch(e){if(e instanceof G){if(e.p>r)continue;e.p!==r&&(r=e.p,n.length=0)}else r=-1;n.push(e)}throw 1===n.length?n[0]:new AggregateError(n)},_=e=>(t,o)=>{if(!Array.isArray(t))throw new G("expected list, got "+typeof t,o);return t.map((t,n)=>e(t,{...o,path:`${o.path}[${n}]`}))},P=(e,t)=>{if("boolean"!=typeof e)throw new G("expected boolean, got "+typeof e,t);return e},C=(e,t,o)=>(n,r)=>{if("number"!=typeof n)throw new G("expected number, got "+typeof n,r);if(e&&(0|n)!==n)throw new G(`expected integer, got ${n}`,r);if("number"==typeof t&&n<t)throw new G(`value cannot be less than ${t}`,r);if("number"==typeof o&&n>o)throw new G(`value cannot be greater than ${o}`,r);return n},I=()=>(e,t)=>{if("string"!=typeof e)throw new G("expected string, got "+typeof e,t);try{return new RegExp(e)}catch{throw new G("expected regular expression",t)}},H=(e,t)=>(r,s)=>{if("string"!=typeof r)throw new G("expected string, got "+typeof r,s);if(e&&!e.test(r))throw new G(`expected string matching ${e}`,s);if("uri-reference"===t&&s.file){if(r.startsWith("file://"))return"file://"+n(o(s.file),r.substring(7));if(!r.includes("://"))return n(o(s.file),r)}return r},B=(e,t,o)=>(n,r)=>{if("object"!=typeof n)throw new G("expected object, got "+typeof n,r);if(!n)throw new G("expected object, got null",r);if(Array.isArray(n))throw new G("expected object, got list",r);const s=[],i=new Set;for(const[o,c]of Object.entries(n)){i.add(o);const n=(e.get(o)??t)(c,{...r,path:`${r.path}.${o}`});void 0!==n&&s.push([o,n])}for(const e of o)if(!i.has(e))throw new G(`missing required property ${JSON.stringify(e)}`,r);for(const[t,o]of e)if(!i.has(t)){const e=o(void 0,{...r,path:`${r.path}.${t}`});void 0!==e&&s.push([t,e])}return Object.fromEntries(s)},U=e=>e,q=(e,t)=>{throw new G("unknown property",t)};class G extends Error{constructor(e,t,o=0){super(`${e} at ${t.path||"root"}`),this.p=o}}const D=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0,L=(e,t,o="raw")=>e.replaceAll(/\$\{(?:(raw|html|json|int|uri)\()?([^${}():]+)(?:(\))?:-((?:[^})\\]|\\.)*))?(\))?\}/g,(e,n,r,s,i,c)=>{if((s?1:0)+(c?1:0)!=(n?1:0))return e;const a=t(r),f=K[n??o];let p=a.t;return s&&p&&f&&(p=f(p)),p??=i?.replaceAll(/\\(.)/g,"$1")??"",c&&f&&(p=f(p)),!n&&f&&a.o!==o&&(p=f(p)),W(p)}),K={html:e=>W(e).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;"),json:e=>JSON.stringify(W(e)),int:e=>{if(Array.isArray(e))return"0";const t=/^(?:\+|(-))?0*(\d+)$/.exec(e);return t?(t[1]??"")+t[2]:"0"},uri:e=>Array.isArray(e)?e.map(encodeURIComponent):encodeURIComponent(e)},W=e=>Array.isArray(e)?e.join("/"):e,Z=e=>t=>"?"===t?{t:g(e)||void 0,o:"uri"}:"?"===t[0]?{t:m(e,t.substring(1)),o:"raw"}:{t:y(e,t),o:"raw"};class F{constructor(e,t){this.i=!1,this.l=!1,this.u=!1,this.h=e,this.m=t,this.$=new Map,this.v=new Set}async set(e,t){if(!this.l&&!this.u)try{this.l=!0;const o=[],n=[],r=this.v;this.v=new Set;e:for(let e=0;e<t.length;++e){const o=t[e];for(const e of r)if(Y(e.config,o)){this.v.add(e),r.delete(e);continue e}n.push(async()=>{this.v.add(await this.S(o))})}for(const e of r)o.push(e.close);const s=new Set;for(let t=0;t<e.length;++t){const o=e[t],r=o.port;r<=0||r>65535?this.h(0,`servers[${t}] must have a specific port from 1 to 65535`):s.has(r)?this.h(0,`skipping servers[${t}] because port ${r} has already been defined`):(s.add(r),n.push(async()=>{const e=await this.k(o,this.$.get(r));e?this.$.set(r,e):this.$.delete(r)}))}this.i||=n.length>0;for(const[e,t]of this.$)s.has(e)||(n.push(t.close),this.$.delete(e));const i=async e=>{let t=[];if(await Promise.all(e.map(async e=>{try{await e()}catch(e){t.push(e)}})),t.length)throw await this.O(),t.length>1?new AggregateError(t):t[0]};await i(o),await i(n),this.u?this.O():this.$.size?this.h(1,"all servers ready"):this.h(1,"no servers configured")}finally{this.l=!1}}async S(e){const t=this.m("35",e.command),o=new AbortController;return new Promise((n,r)=>{this.h(2,`${t} ${this.m("2","starting")}`);const i=s(e.command,e.arguments,{cwd:e.cwd,env:{...process.env,TERM:"",COLORTERM:"",NO_COLOR:"1",...e.environment},killSignal:e.options.killSignal,uid:e.options.uid,gid:e.options.gid,stdio:["ignore","pipe","pipe"],signal:o.signal});e.options.displayStdout?V(i.stdout,`${t} ${this.m("2","[stdout]")} `,process.stderr):i.stdout.resume(),e.options.displayStderr?V(i.stderr,`${t} ${this.m("2","[stderr]")} `,process.stderr):i.stderr.resume(),i.addListener("error",e=>{this.h(0,`${t} startup failed: ${e.message}`),r(e)}),i.addListener("exit",(e,o)=>{null!==e?this.h(2,`${t} closed ${this.m("2",`(exit code ${e})`)}`):this.h(2,`${t} closed ${this.m("2",`(exit signal ${o})`)}`)}),i.addListener("spawn",()=>n({config:e,close:()=>new Promise(o=>{null!==i.signalCode||null!==i.exitCode?o():(this.h(2,`${t} ${this.m("2",`closing (signal ${e.options.killSignal})`)}`),i.addListener("exit",o),i.kill(e.options.killSignal))})}))})}async k({port:e,host:t,options:o,mount:n},r){const s=this.m("34",`http://${t}:${e}`),i=await(async(e,t=()=>{})=>{const o=new a;o.use(f((e,o)=>{const n=Date.now();return p(e,()=>{const r=Date.now()-n;t({method:e.method??"GET",path:e.url??"/",status:o.statusCode,duration:r})}),l}));for(const t of e)switch(t.type){case"files":if("/dev/null"!==t.dir){const e=t.options.negotiation&&t.options.negotiation.length>0?new h(t.options.negotiation):void 0,n=new Map(Object.entries(t.options.headers??{}));o.mount(t.path,await d(t.dir,{...t.options,negotiator:e,callback:(e,t,o)=>{"etag"in n||t.setHeader("etag",w(t.getHeader("content-encoding"),o.stats)),"last-modified"in n||t.setHeader("last-modified",o.stats.mtime.toUTCString()),t.setHeaders(n)}}))}break;case"proxy":o.mount(t.path,u(t.target,{...t.options,responseHeaders:[(e,o,n)=>({...n,...t.options.headers})]}));break;case"fixture":const e=(e,o)=>{for(const[n,r]of Object.entries(t.headers))"string"==typeof r?o.setHeader(n,L(r,Z(e))):"number"==typeof r?o.setHeader(n,r):o.setHeader(n,r.map(t=>L(t,Z(e))));o.statusCode=t.status,o.end(L(t.body,Z(e)))};"GET"===t.method&&o.onRequest("HEAD",t.path,e),o.onRequest(t.method,t.path,e);break;case"redirect":o.at(t.path,f((e,o)=>{let n=L(t.target,Z(e),"uri");"/"===t.target[0]&&(n=n.replace(/^\/{2,}/,"/")),o.setHeader("location",n),o.statusCode=t.status,o.end()}))}return o})(n,o.logRequests?e=>{const t=this.m("1",e.method.replaceAll(/[^a-zA-Z0-9\-_]/g,"?")),o=encodeURI(e.path),n=this.m(Q[e.status/100|0]??"",String(e.status)),r=this.m("2",`(${e.duration}ms)`);this.h(0,`${s} ${t} ${o} ${n} ${r}`)}:()=>{}),g=new $(i);let m,y;if(g.addEventListener("error",e=>{e.preventDefault();const{error:t,context:o,request:n}=e.detail;(b(t,v)?.statusCode??500)>=500&&this.h(0,`${s} ${this.m("91","error")}: ${o} ${n?.url} ${t}`)}),r&&t===r.host&&((e,t)=>{for(const o of ee)if(e[o]!==t[o])return!1;return!0})(o,r.options))m=r.server,this.h(2,`${s} updated`),r.detach();else{if(r?(this.h(2,`${s} ${this.m("2","restarting (step 1: shutdown)")}`),await r.close(),this.h(2,`${s} ${this.m("2","restarting (step 2: start)")}`)):this.h(2,`${s} ${this.m("2","starting")}`),this.u)return;m=c(o),m.setTimeout(o.socketTimeout),y=X(m,e,t,o.backlog)}const x=g.attach(m,o);return y&&(await y,await new Promise(e=>setTimeout(e,1)),this.h(2,`${s} ready`)),{host:t,options:o,server:m,detach:()=>x("restart",o.restartTimeout),close:()=>new Promise(e=>{const t=x("shutdown",o.shutdownTimeout,!0,()=>{m.close(()=>{this.h(2,`${s} closed`),e()}),m.closeAllConnections()}).countConnections();t>0&&this.h(2,`${s} ${this.m("2",`closing (remaining connections: ${t})`)}`)})}}async O(){this.$.size&&(this.h(2,this.m("2","shutting down")),await Promise.all([...this.$.values(),...this.v].map(e=>e.close()))),this.i&&this.h(2,"shutdown complete")}shutdown(){this.u||(this.u=!0,this.l||this.O())}}const Q=["","37","32","36","31","41;97"];function V(e,t,o){let n="";const r=e=>o.write(t+e.replaceAll(/\x1b\[[\d;]*[A-K]/g,"").replaceAll(/[\x00-\x1f]/g,e=>`\\x${e.charCodeAt(0).toString(16).padStart(2,"0")}`)+"\n");e.addListener("data",e=>{n+=e;const t=n.split("\n");n=t.pop();for(const e of t)r(e)}),e.addListener("end",()=>{n&&r(n)})}const X=async(e,t,o,n=511)=>new Promise((r,s)=>{e.once("error",s),e.listen(t,o,n,()=>{e.off("error",s),r()})});function Y(e,t){return e.command===t.command&&e.arguments.length===t.arguments.length&&e.arguments.every((e,o)=>e===t.arguments[o])&&e.cwd===t.cwd&&JSON.stringify(e.environment)===JSON.stringify(t.environment)&&e.options.uid===t.options.uid&&e.options.gid===t.options.gid&&e.options.killSignal===t.options.killSignal&&e.options.displayStdout===t.options.displayStdout&&e.options.displayStderr===t.options.displayStderr}const ee=["requestTimeout","keepAliveTimeout","keepAliveTimeoutBuffer","connectionsCheckingInterval","headersTimeout","highWaterMark","maxHeaderSize","noDelay","requireHostHeader","keepAlive","keepAliveInitialDelay","uniqueHeaders","backlog","socketTimeout"];function te(e){return e.reduce((e,t)=>({rawSize:e.rawSize+t.rawSize,bestSize:e.bestSize+t.bestSize,created:e.created+t.created}),{rawSize:0,bestSize:0,created:0})}const oe=e=>e<2e3?`${e}B`.padStart(8," "):`${(e/1024).toFixed(1)}kB`.padStart(8," "),ne=["none","ready","progress"];process.on("SIGUSR1",()=>{});let re=2;const se=(e,t)=>e<=re&&process.stderr.write(t+"\n");function ie(e){process.stdin.destroy(),se(0,e instanceof Error?e.message:String(e))}process.on("unhandledRejection",ie),process.on("uncaughtException",ie);const ce=process.stderr.isTTY&&!process.env.NO_COLOR?(e,t)=>e?`\x1b[${e}m${t}\x1b[0m`:t:(e,t)=>t,ae=(e=>{const t=[];for(let o=0;o<e.length;++o){const n=e[o];if("--"===n)continue;const r=/^--([^ =\-][^ =]*)=(.*)$/.exec(n);if(r){t.push([r[1],r[2]]);continue}const s=/^-([^ =]*)([^ =])=(.*)$/.exec(n);if(s&&"-"!==n[1]){for(const e of s[1])t.push(["-"+e,""]);t.push(["-"+s[2],s[3]]);continue}if("-"!==n[0]||"-"===n){if(0!==o)throw new Error(`value without key: ${n}`);t.push(["",n]);continue}let i=e[o+1];if(i&&"-"===i[0]&&i.length>1&&(i=void 0),void 0!==i&&++o,"-"===n[1])t.push([n.slice(2),i??""]);else{for(const e of n.slice(1,n.length-1))t.push(["-"+e,""]);t.push(["-"+n[n.length-1],i??""])}}const o=new Map;for(const[e,n]of t){const t=(z.get(e)??e).toLowerCase(),r=A.get(t);if(!r)throw new Error(`unknown flag: ${e}`);let s;switch(r.type){case"string":s=n;break;case"number":s=Number.parseFloat(n);break;case"boolean":s=["","on","true","yes","y","1"].includes(n.toLowerCase())}if(r.multi){let e=o.get(t);e||(e=[],o.set(t,e)),e.push(s)}else{if(o.has(t))throw new Error(`multiple values for ${t}`);o.set(t,s)}}return o})(process.argv.slice(2)),fe=o(new URL(import.meta.url).pathname);if(ae.get("version")||ae.get("help")){let o={name:"web-listener",version:"unknown"};try{o=JSON.parse(await e(t(fe,"package.json"),"utf-8"))}catch{}ae.get("help")?i("man",["-M",fe,o.name],{stdio:["inherit","inherit","inherit"]}):process.stdout.write(`${o.name} ${o.version}\n`),process.exit(0)}(async()=>{const n=new F(se,ce);process.on("unhandledRejection",()=>n.shutdown()),process.on("uncaughtException",()=>n.shutdown());const s=function(e){const t=o=>{const n=o.$ref;if(n&&n.startsWith("#/$defs/")){const t=D(e.$defs,n.substring(8));if(!t)throw new Error(`unable to find ${n} in schema`);o={...t,...o}}const r=((e,t)=>{if(void 0!==e.const)return M(e.const);if(e.enum)return J(new Set(e.enum));if(e.anyOf)return T(e.anyOf.map(t));switch(e.type){case"array":return _(t(e.items));case"boolean":return P;case"integer":return C(!0,e.minimum,e.maximum);case"number":return C(!1,e.minimum,e.maximum);case"object":const o=e.additionalProperties??!0,n=B(new Map(Object.entries(e.properties??{}).map(([e,o])=>[e,t(o)])),"object"==typeof o?t(o):o?U:q,e.required??[]);if(e.$comment?.startsWith("flatten:")){const t=e.$comment.substring(8);return(e,o)=>n(e,o)[t]}return n;case"string":if("regex"===e.format)return I();let r=null;return e.pattern&&(r=new RegExp(e.pattern)),H(r,e.format??"");default:throw new Error(`unknown part type ${JSON.stringify(e)}`)}})(o,t);if(void 0!==o.default){const e=JSON.stringify(o.default);return(t,o)=>r(void 0===t?JSON.parse(e):t,o)}return(e,t)=>void 0===e?void 0:r(e,t)};return t(e)}(await(async()=>JSON.parse(await e(t(o(new URL(import.meta.url).pathname),"schema.json"),"utf-8")))());function i(){process.stdin.destroy(),n.shutdown()}async function c(){const t=await(async(t,o)=>{const n=e=>o.get(e)??[],r=e=>o.get(e),s=e=>o.get(e),i=r("config-file"),c=r("config-json"),a=s("port"),f=r("host"),p=r("dir")||".",l=n("exec").map(e=>e.split(" ")),u=n("ext").map(e=>e.startsWith(".")?e:`.${e}`),h=n("header").map(e=>(e=>{const t=e.match(/: ?/);return t?[e.substring(0,t.index),e.substring(t.index+t[0].length)]:[e]})(e)),d=r("404"),w=r("spa"),g=r("proxy"),m=s("min-compress"),y=n("mime"),$=n("mime-types"),b=r("log");if(Number(Boolean(i))+Number(Boolean(c))+Number(Boolean(g))>1)throw new Error("multiple config files are not supported");let v;if(i)v=t(JSON.parse(await e(i,"utf-8")),{file:i,path:""});else if(c)v=t(JSON.parse(c),{file:"",path:""});else{let e;w?e={statusCode:200,filePath:w}:d&&(e={statusCode:404,filePath:d});const o=[{type:"files",dir:p,options:{fallback:e}}];g&&o.push({type:"proxy",target:g}),v=t({servers:[{port:8080,mount:o}]},{file:"",path:""})}for(const e of l)v.backgroundTasks.push({command:e[0],arguments:e.slice(1),cwd:process.cwd(),environment:{},options:{killSignal:"SIGTERM",displayStdout:!0,displayStderr:!0}});const x=1===v.servers.length?v.servers[0]:void 0;if(void 0!==a){if((0|a)!==a)throw new Error("port must be an integer");if(!x)throw new Error("cannot specify port on commandline when defining multiple servers");x.port=a}if(void 0!==f)for(const e of v.servers)e.host=f;const S=(e,t)=>{for(const o of v.servers)for(const n of o.mount)if("files"===n.type){n.options.negotiation??=[];let o=n.options.negotiation.find(t=>t.feature===e);o||(o={feature:e,options:[]},n.options.negotiation=[...n.options.negotiation,o]),o.options.find(e=>e.value===t.value)||o.options.push(t)}};for(const[e,t]of R)o.get(e)&&S("encoding",t);if(u.length)for(const e of v.servers)for(const t of e.mount)"files"===t.type&&(t.options.implicitSuffixes=u);if(h.length)for(const e of v.servers)for(const t of e.mount)"files"!==t.type&&"proxy"!==t.type||(t.options.headers=N(t.options.headers,h));switch((y.length||$.length)&&(Array.isArray(v.mime)||(v.mime?v.mime=[v.mime]:v.mime=[]),v.mime.push(...y),v.mime.push(...$.map(e=>`file://${e}`))),o.get("write-compressed")&&(v.writeCompressed=!0),void 0!==m&&(v.minCompress=m),o.get("no-serve")&&(v.noServe=!0),b){case"none":case"ready":case"progress":v.log=b;for(const e of v.servers)e.options.logRequests=!1;for(const e of v.backgroundTasks)e.options.displayStderr=!1,e.options.displayStdout=!1;break;case"full":v.log="progress"}return v})(s,ae);re=ne.indexOf(t.log),await(async t=>{const o=[];for(const n of Array.isArray(t)?t:[t])"string"!=typeof n?o.push(new Map(Object.entries(n))):n.startsWith("file://")?o.push(k(await e(n.substring(7),"utf-8"))):o.push(O(n));j();for(const e of o)E(e)})(t.mime),t.writeCompressed&&await(async(e,t,o)=>{let n=0;for(const s of e)for(const e of s.mount)if("files"===e.type){const s=e.options.negotiation?.find(e=>"encoding"===e.feature);if(!s?.options?.length){o(2,`skipping ${e.dir} because no compression is configured`);continue}const i=s.match?` matching ${s.match}`:"";o(2,`compressing files in ${e.dir}${i} using ${s.options.map(e=>e.value).join(", ")}`);const c=x(s.match,!0),a=await S(e.dir,s.options,{minCompression:t,filter:(e,t)=>!["image","video","audio","font"].includes(t.split("/")[0])&&c(r(e))}),f=te(a.filter(({mime:e})=>e.startsWith("text/"))),p=te(a.filter(({mime:e})=>!e.startsWith("text/")));o(2,`text: ${oe(f.rawSize)} / ${oe(f.bestSize)} compressed`),o(2,`other: ${oe(p.rawSize)} / ${oe(p.bestSize)} compressed`),n+=f.created+p.created}o(2,`${((e,t,o=t+"s")=>1===e?`1 ${t}`:`${e} ${o}`)(n,"compressed file")} written`)})(t.servers,t.minCompress,se),t.noServe?i():n.set(t.servers,t.backgroundTasks).catch(e=>{if(e instanceof AggregateError)for(const t of e.errors)se(0,t instanceof Error?t.message:String(t));else se(0,e instanceof Error?e.message:String(e));process.stdin.destroy(),process.exit(1)})}function a(){return se(2,"refreshing config"),c()}c(),process.on("SIGHUP",()=>a()),process.stdin.on("data",e=>{e.includes("\n")&&a()});let f=!1;process.on("SIGINT",()=>{f||(f=!0,se(2,""),i())})})();
2
+ import{readFile as e}from"node:fs/promises";import{join as t,dirname as o,resolve as n,basename as r}from"node:path";import{spawn as s,spawnSync as i}from"node:child_process";import{createServer as c}from"node:http";import{Router as a,requestHandler as f,addTeardown as p,CONTINUE as l,proxy as u,Negotiator as h,fileServer as d,generateWeakETag as w,getSearch as g,getQuery as m,getPathParameter as y,WebListener as $,findCause as b,HTTPError as v,stringPredicate as x,compressFilesInDir as S,readMimeTypes as k,decompressMime as O,resetMime as j,registerMime as E}from"./index.js";const z=new Map([["","dir"],["-a","host"],["-b","brotli"],["-c","config-file"],["-C","config-json"],["-d","dir"],["-e","ext"],["-g","gzip"],["-h","help"],["-H","header"],["-p","port"],["-P","proxy"],["-v","version"],["br","brotli"],["gz","gzip"],["zst","zstd"]]),A=new Map([["config-file",{type:"string"}],["config-json",{type:"string"}],["dir",{type:"string",multi:!0}],["exec",{type:"string",multi:!0}],["ext",{type:"string",multi:!0}],["port",{type:"number"}],["host",{type:"string"}],["zstd",{type:"boolean"}],["brotli",{type:"boolean"}],["gzip",{type:"boolean"}],["deflate",{type:"boolean"}],["proxy",{type:"string"}],["404",{type:"string"}],["spa",{type:"string"}],["header",{type:"string",multi:!0}],["mime",{type:"string",multi:!0}],["mime-types",{type:"string",multi:!0}],["write-compressed",{type:"boolean"}],["min-compress",{type:"number"}],["no-serve",{type:"boolean"}],["log",{type:"string"}],["help",{type:"boolean"}],["version",{type:"boolean"}]]);function N(e,t){const o=new Map(Object.entries(e??{}).map(([e,t])=>[e,Array.isArray(t)?[...t]:[t]]));for(const[e,n=""]of t){const t=o.get(e);t?t.push(n):o.set(e,[n])}return Object.fromEntries(o.entries())}const R=new Map([["zstd",{value:"zstd",file:"{file}.zst"}],["brotli",{value:"br",file:"{file}.br"}],["gzip",{value:"gzip",file:"{file}.gz"}],["deflate",{value:"deflate",file:"{file}.deflate"}]]);const M=e=>(t,o)=>{if(t!==e)throw new G(`expected ${JSON.stringify(e)}`,o,8);return e},J=e=>(t,o)=>{if(!e.has(t))throw new G(`expected one of ${JSON.stringify(e)}`,o,7);return t},T=e=>(t,o)=>{const n=[];let r=9;for(const s of e)try{return s(t,o)}catch(e){if(e instanceof G){if(e.p>r)continue;e.p!==r&&(r=e.p,n.length=0)}else r=-1;n.push(e)}throw 1===n.length?n[0]:new AggregateError(n)},_=e=>(t,o)=>{if(!Array.isArray(t))throw new G("expected list, got "+typeof t,o);return t.map((t,n)=>e(t,{...o,path:`${o.path}[${n}]`}))},P=(e,t)=>{if("boolean"!=typeof e)throw new G("expected boolean, got "+typeof e,t);return e},C=(e,t,o)=>(n,r)=>{if("number"!=typeof n)throw new G("expected number, got "+typeof n,r);if(e&&(0|n)!==n)throw new G(`expected integer, got ${n}`,r);if("number"==typeof t&&n<t)throw new G(`value cannot be less than ${t}`,r);if("number"==typeof o&&n>o)throw new G(`value cannot be greater than ${o}`,r);return n},I=()=>(e,t)=>{if("string"!=typeof e)throw new G("expected string, got "+typeof e,t);try{return new RegExp(e)}catch{throw new G("expected regular expression",t)}},H=(e,t)=>(r,s)=>{if("string"!=typeof r)throw new G("expected string, got "+typeof r,s);if(e&&!e.test(r))throw new G(`expected string matching ${e}`,s);if("uri-reference"===t&&s.file){if(r.startsWith("file://"))return"file://"+n(o(s.file),r.substring(7));if(!r.includes("://"))return n(o(s.file),r)}return r},B=(e,t,o)=>(n,r)=>{if("object"!=typeof n)throw new G("expected object, got "+typeof n,r);if(!n)throw new G("expected object, got null",r);if(Array.isArray(n))throw new G("expected object, got list",r);const s=[],i=new Set;for(const[o,c]of Object.entries(n)){i.add(o);const n=(e.get(o)??t)(c,{...r,path:`${r.path}.${o}`});void 0!==n&&s.push([o,n])}for(const e of o)if(!i.has(e))throw new G(`missing required property ${JSON.stringify(e)}`,r);for(const[t,o]of e)if(!i.has(t)){const e=o(void 0,{...r,path:`${r.path}.${t}`});void 0!==e&&s.push([t,e])}return Object.fromEntries(s)},U=e=>e,q=(e,t)=>{throw new G("unknown property",t)};class G extends Error{constructor(e,t,o=0){super(`${e} at ${t.path||"root"}`),this.p=o}}const D=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0,L=(e,t,o="raw")=>e.replaceAll(/\$\{(?:(raw|html|json|int|uri)\()?([^${}():]+)(?:(\))?:-((?:[^})\\]|\\.)*))?(\))?\}/g,(e,n,r,s,i,c)=>{if((s?1:0)+(c?1:0)!=(n?1:0))return e;const a=t(r),f=K[n??o];let p=a.t;return s&&p&&f&&(p=f(p)),p??=i?.replaceAll(/\\(.)/g,"$1")??"",c&&f&&(p=f(p)),!n&&f&&a.o!==o&&(p=f(p)),W(p)}),K={html:e=>W(e).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;"),json:e=>JSON.stringify(W(e)),int:e=>{if(Array.isArray(e))return"0";const t=/^(?:\+|(-))?0*(\d+)$/.exec(e);return t?(t[1]??"")+t[2]:"0"},uri:e=>Array.isArray(e)?e.map(encodeURIComponent):encodeURIComponent(e)},W=e=>Array.isArray(e)?e.join("/"):e,Z=e=>t=>"?"===t?{t:g(e)||void 0,o:"uri"}:"?"===t[0]?{t:m(e,t.substring(1)),o:"raw"}:{t:y(e,t),o:"raw"};class F{constructor(e,t){this.i=!1,this.l=!1,this.u=!1,this.h=e,this.m=t,this.$=new Map,this.v=new Set}async set(e,t){if(!this.l&&!this.u)try{this.l=!0;const o=[],n=[],r=this.v;this.v=new Set;e:for(let e=0;e<t.length;++e){const o=t[e];for(const e of r)if(Y(e.config,o)){this.v.add(e),r.delete(e);continue e}n.push(async()=>{this.v.add(await this.S(o))})}for(const e of r)o.push(e.close);const s=new Set;for(let t=0;t<e.length;++t){const o=e[t],r=o.port;r<=0||r>65535?this.h(0,`servers[${t}] must have a specific port from 1 to 65535`):s.has(r)?this.h(0,`skipping servers[${t}] because port ${r} has already been defined`):(s.add(r),n.push(async()=>{const e=await this.k(o,this.$.get(r));e?this.$.set(r,e):this.$.delete(r)}))}this.i||=n.length>0;for(const[e,t]of this.$)s.has(e)||(n.push(t.close),this.$.delete(e));const i=async e=>{let t=[];if(await Promise.all(e.map(async e=>{try{await e()}catch(e){t.push(e)}})),t.length)throw await this.O(),t.length>1?new AggregateError(t):t[0]};await i(o),await i(n),this.u?this.O():this.$.size?this.h(1,"all servers ready"):this.h(1,"no servers configured")}finally{this.l=!1}}async S(e){const t=this.m("35",e.command),o=new AbortController;return new Promise((n,r)=>{this.h(2,`${t} ${this.m("2","starting")}`);const i=s(e.command,e.arguments,{cwd:e.cwd,env:{...process.env,TERM:"",COLORTERM:"",NO_COLOR:"1",...e.environment},killSignal:e.options.killSignal,uid:e.options.uid,gid:e.options.gid,stdio:["ignore","pipe","pipe"],signal:o.signal});e.options.displayStdout?V(i.stdout,`${t} ${this.m("2","[stdout]")} `,process.stderr):i.stdout.resume(),e.options.displayStderr?V(i.stderr,`${t} ${this.m("2","[stderr]")} `,process.stderr):i.stderr.resume(),i.addListener("error",e=>{this.h(0,`${t} startup failed: ${e.message}`),r(e)}),i.addListener("exit",(e,o)=>{null!==e?this.h(2,`${t} closed ${this.m("2",`(exit code ${e})`)}`):this.h(2,`${t} closed ${this.m("2",`(exit signal ${o})`)}`)}),i.addListener("spawn",()=>n({config:e,close:()=>new Promise(o=>{null!==i.signalCode||null!==i.exitCode?o():(this.h(2,`${t} ${this.m("2",`closing (signal ${e.options.killSignal})`)}`),i.addListener("exit",o),i.kill(e.options.killSignal))})}))})}async k({port:e,host:t,options:o,mount:n},r){const s=this.m("34",`http://${t}:${e}`),i=await(async(e,t=()=>{})=>{const o=new a;o.use(f((e,o)=>{const n=Date.now();return p(e,()=>{const r=Date.now()-n;t({method:e.method??"GET",path:e.url??"/",status:o.statusCode,duration:r})}),l}));for(const t of e)switch(t.type){case"files":if("/dev/null"!==t.dir){const e=t.options.negotiation&&t.options.negotiation.length>0?new h(t.options.negotiation):void 0,n=new Map(Object.entries(t.options.headers??{}));o.mount(t.path,await d(t.dir,{...t.options,negotiator:e,callback:(e,t,o)=>{"etag"in n||t.setHeader("etag",w(t.getHeader("content-encoding"),o.stats)),"last-modified"in n||t.setHeader("last-modified",o.stats.mtime.toUTCString()),t.setHeaders(n)}}))}break;case"proxy":o.mount(t.path,u(t.target,{...t.options,responseHeaders:[(e,o,n)=>({...n,...t.options.headers})]}));break;case"fixture":const e=(e,o)=>{for(const[n,r]of Object.entries(t.headers))"string"==typeof r?o.setHeader(n,L(r,Z(e))):"number"==typeof r?o.setHeader(n,r):o.setHeader(n,r.map(t=>L(t,Z(e))));o.statusCode=t.status,o.end(L(t.body,Z(e)))};"GET"===t.method&&o.onRequest("HEAD",t.path,e),o.onRequest(t.method,t.path,e);break;case"redirect":o.at(t.path,f((e,o)=>{let n=L(t.target,Z(e),"uri");"/"===t.target[0]&&(n=n.replace(/^\/{2,}/,"/")),o.setHeader("location",n),o.statusCode=t.status,o.end()}))}return o})(n,o.logRequests?e=>{const t=this.m("1",e.method.replaceAll(/[^a-zA-Z0-9\-_]/g,"?")),o=encodeURI(e.path),n=this.m(Q[e.status/100|0]??"",String(e.status)),r=this.m("2",`(${e.duration}ms)`);this.h(0,`${s} ${t} ${o} ${n} ${r}`)}:()=>{}),g=new $(i);let m,y;if(g.addEventListener("error",e=>{e.preventDefault();const{error:t,context:o,request:n}=e.detail;(b(t,v)?.statusCode??500)>=500&&this.h(0,`${s} ${this.m("91","error")}: ${o} ${n?.url} ${t}`)}),r&&t===r.host&&((e,t)=>{for(const o of ee)if(e[o]!==t[o])return!1;return!0})(o,r.options))m=r.server,this.h(2,`${s} updated`),r.detach();else{if(r?(this.h(2,`${s} ${this.m("2","restarting (step 1: shutdown)")}`),await r.close(),this.h(2,`${s} ${this.m("2","restarting (step 2: start)")}`)):this.h(2,`${s} ${this.m("2","starting")}`),this.u)return;m=c(o),m.setTimeout(o.socketTimeout),y=X(m,e,t,o.backlog)}const x=g.attach(m,o);return y&&(await y,await new Promise(e=>setTimeout(e,1)),this.h(2,`${s} ready`)),{host:t,options:o,server:m,detach:()=>x("restart",o.restartTimeout),close:()=>new Promise(e=>{const t=x("shutdown",o.shutdownTimeout,!0,()=>{m.close(()=>{this.h(2,`${s} closed`),e()}),m.closeAllConnections()}).countConnections();t>0&&this.h(2,`${s} ${this.m("2",`closing (remaining connections: ${t})`)}`)})}}async O(){this.$.size&&(this.h(2,this.m("2","shutting down")),await Promise.all([...this.$.values(),...this.v].map(e=>e.close()))),this.i&&this.h(2,"shutdown complete")}shutdown(){this.u||(this.u=!0,this.l||this.O())}}const Q=["","37","32","36","31","41;97"];function V(e,t,o){let n="";const r=e=>o.write(t+e.replaceAll(/\x1b\[[\d;]*[A-K]/g,"").replaceAll(/[\x00-\x1f]/g,e=>`\\x${e.charCodeAt(0).toString(16).padStart(2,"0")}`)+"\n");e.addListener("data",e=>{n+=e;const t=n.split("\n");n=t.pop();for(const e of t)r(e)}),e.addListener("end",()=>{n&&r(n)})}const X=async(e,t,o,n=511)=>new Promise((r,s)=>{e.once("error",s),e.listen(t,o,n,()=>{e.off("error",s),r()})});function Y(e,t){return e.command===t.command&&e.arguments.length===t.arguments.length&&e.arguments.every((e,o)=>e===t.arguments[o])&&e.cwd===t.cwd&&JSON.stringify(e.environment)===JSON.stringify(t.environment)&&e.options.uid===t.options.uid&&e.options.gid===t.options.gid&&e.options.killSignal===t.options.killSignal&&e.options.displayStdout===t.options.displayStdout&&e.options.displayStderr===t.options.displayStderr}const ee=["requestTimeout","keepAliveTimeout","keepAliveTimeoutBuffer","connectionsCheckingInterval","headersTimeout","highWaterMark","maxHeaderSize","noDelay","requireHostHeader","keepAlive","keepAliveInitialDelay","uniqueHeaders","backlog","socketTimeout"];function te(e){return e.reduce((e,t)=>({rawSize:e.rawSize+t.rawSize,bestSize:e.bestSize+t.bestSize,created:e.created+t.created}),{rawSize:0,bestSize:0,created:0})}const oe=e=>e<2e3?`${e}B`.padStart(8," "):`${(e/1024).toFixed(1)}kB`.padStart(8," "),ne=["none","ready","progress"];process.on("SIGUSR1",()=>{});let re=2;const se=(e,t)=>e<=re&&process.stderr.write(t+"\n");function ie(e){process.stdin.destroy(),se(0,e instanceof Error?e.message:String(e))}process.on("unhandledRejection",ie),process.on("uncaughtException",ie);const ce=process.stderr.isTTY&&!process.env.NO_COLOR?(e,t)=>e?`\x1b[${e}m${t}\x1b[0m`:t:(e,t)=>t,ae=(e=>{const t=[];for(let o=0;o<e.length;++o){const n=e[o];if("--"===n)continue;const r=/^--([^ =\-][^ =]*)=(.*)$/.exec(n);if(r){t.push([r[1],r[2]]);continue}const s=/^-([^ =]*)([^ =])=(.*)$/.exec(n);if(s&&"-"!==n[1]){for(const e of s[1])t.push(["-"+e,""]);t.push(["-"+s[2],s[3]]);continue}if("-"!==n[0]||"-"===n){if(0!==o)throw new Error(`value without key: ${n}`);t.push(["",n]);continue}let i=e[o+1];if(i&&"-"===i[0]&&i.length>1&&(i=void 0),void 0!==i&&++o,"-"===n[1])t.push([n.slice(2),i??""]);else{for(const e of n.slice(1,n.length-1))t.push(["-"+e,""]);t.push(["-"+n[n.length-1],i??""])}}const o=new Map;for(const[e,n]of t){const t=(z.get(e)??e).toLowerCase(),r=A.get(t);if(!r)throw new Error(`unknown flag: ${e}`);let s;switch(r.type){case"string":s=n;break;case"number":s=Number.parseFloat(n);break;case"boolean":s=["","on","true","yes","y","1"].includes(n.toLowerCase())}if(r.multi){let e=o.get(t);e||(e=[],o.set(t,e)),e.push(s)}else{if(o.has(t))throw new Error(`multiple values for ${t}`);o.set(t,s)}}return o})(process.argv.slice(2)),fe=o(new URL(import.meta.url).pathname);if(ae.get("version")||ae.get("help")){let o={name:"web-listener",version:"unknown"};try{o=JSON.parse(await e(t(fe,"package.json"),"utf-8"))}catch{}ae.get("help")?i("man",["-M",fe,o.name],{stdio:["inherit","inherit","inherit"]}):process.stdout.write(`${o.name} ${o.version}\n`),process.exit(0)}(async()=>{const n=new F(se,ce);process.on("unhandledRejection",()=>n.shutdown()),process.on("uncaughtException",()=>n.shutdown());const s=function(e){const t=o=>{const n=o.$ref;if(n&&n.startsWith("#/$defs/")){const t=D(e.$defs,n.substring(8));if(!t)throw new Error(`unable to find ${n} in schema`);o={...t,...o}}const r=((e,t)=>{if(void 0!==e.const)return M(e.const);if(e.enum)return J(new Set(e.enum));if(e.anyOf)return T(e.anyOf.map(t));switch(e.type){case"array":return _(t(e.items));case"boolean":return P;case"integer":return C(!0,e.minimum,e.maximum);case"number":return C(!1,e.minimum,e.maximum);case"object":const o=e.additionalProperties??!0,n=B(new Map(Object.entries(e.properties??{}).map(([e,o])=>[e,t(o)])),"object"==typeof o?t(o):o?U:q,e.required??[]);if(e.$comment?.startsWith("flatten:")){const t=e.$comment.substring(8);return(e,o)=>n(e,o)[t]}return n;case"string":if("regex"===e.format)return I();let r=null;return e.pattern&&(r=new RegExp(e.pattern)),H(r,e.format??"");default:throw new Error(`unknown part type ${JSON.stringify(e)}`)}})(o,t);if(void 0!==o.default){const e=JSON.stringify(o.default);return(t,o)=>r(void 0===t?JSON.parse(e):t,o)}return(e,t)=>void 0===e?void 0:r(e,t)};return t(e)}(await(async()=>JSON.parse(await e(t(o(new URL(import.meta.url).pathname),"schema.json"),"utf-8")))());function i(){process.stdin.destroy(),n.shutdown()}async function c(){const t=await(async(t,o)=>{const n=(e,t=[])=>o.get(e)??t,r=e=>o.get(e),s=e=>o.get(e),i=r("config-file"),c=r("config-json"),a=s("port"),f=r("host"),p=n("dir",["."]),l=n("exec").map(e=>e.split(" ")),u=n("ext").map(e=>e.startsWith(".")?e:`.${e}`),h=n("header").map(e=>(e=>{const t=e.match(/: ?/);return t?[e.substring(0,t.index),e.substring(t.index+t[0].length)]:[e]})(e)),d=r("404"),w=r("spa"),g=r("proxy"),m=s("min-compress"),y=n("mime"),$=n("mime-types"),b=r("log");if(Number(Boolean(i))+Number(Boolean(c))+Number(Boolean(g))>1)throw new Error("multiple config files are not supported");let v;if(i)v=t(JSON.parse(await e(i,"utf-8")),{file:i,path:""});else if(c)v=t(JSON.parse(c),{file:"",path:""});else{let e;w?e={statusCode:200,filePath:w}:d&&(e={statusCode:404,filePath:d});const o=[];for(let t=0;t<p.length;++t){const n=p[t],r=t===p.length-1;o.push({type:"files",dir:n,options:{fallback:r?e:void 0}})}g&&o.push({type:"proxy",target:g}),v=t({servers:[{port:8080,mount:o}]},{file:"",path:""})}for(const e of l)v.backgroundTasks.push({command:e[0],arguments:e.slice(1),cwd:process.cwd(),environment:{},options:{killSignal:"SIGTERM",displayStdout:!0,displayStderr:!0}});const x=1===v.servers.length?v.servers[0]:void 0;if(void 0!==a){if((0|a)!==a)throw new Error("port must be an integer");if(!x)throw new Error("cannot specify port on commandline when defining multiple servers");x.port=a}if(void 0!==f)for(const e of v.servers)e.host=f;const S=(e,t)=>{for(const o of v.servers)for(const n of o.mount)if("files"===n.type){n.options.negotiation??=[];let o=n.options.negotiation.find(t=>t.feature===e);o||(o={feature:e,options:[]},n.options.negotiation=[...n.options.negotiation,o]),o.options.find(e=>e.value===t.value)||o.options.push(t)}};for(const[e,t]of R)o.get(e)&&S("encoding",t);if(u.length)for(const e of v.servers)for(const t of e.mount)"files"===t.type&&(t.options.implicitSuffixes=u);if(h.length)for(const e of v.servers)for(const t of e.mount)"files"!==t.type&&"proxy"!==t.type||(t.options.headers=N(t.options.headers,h));switch((y.length||$.length)&&(Array.isArray(v.mime)||(v.mime?v.mime=[v.mime]:v.mime=[]),v.mime.push(...y),v.mime.push(...$.map(e=>`file://${e}`))),o.get("write-compressed")&&(v.writeCompressed=!0),void 0!==m&&(v.minCompress=m),o.get("no-serve")&&(v.noServe=!0),b){case"none":case"ready":case"progress":v.log=b;for(const e of v.servers)e.options.logRequests=!1;for(const e of v.backgroundTasks)e.options.displayStderr=!1,e.options.displayStdout=!1;break;case"full":v.log="progress"}return v})(s,ae);re=ne.indexOf(t.log),await(async t=>{const o=[];for(const n of Array.isArray(t)?t:[t])"string"!=typeof n?o.push(new Map(Object.entries(n))):n.startsWith("file://")?o.push(k(await e(n.substring(7),"utf-8"))):o.push(O(n));j();for(const e of o)E(e)})(t.mime),t.writeCompressed&&await(async(e,t,o)=>{let n=0;for(const s of e)for(const e of s.mount)if("files"===e.type){const s=e.options.negotiation?.find(e=>"encoding"===e.feature);if(!s?.options?.length){o(2,`skipping ${e.dir} because no compression is configured`);continue}const i=s.match?` matching ${s.match}`:"";o(2,`compressing files in ${e.dir}${i} using ${s.options.map(e=>e.value).join(", ")}`);const c=x(s.match,!0),a=await S(e.dir,s.options,{minCompression:t,filter:(e,t)=>!["image","video","audio","font"].includes(t.split("/")[0])&&c(r(e))}),f=te(a.filter(({mime:e})=>e.startsWith("text/"))),p=te(a.filter(({mime:e})=>!e.startsWith("text/")));o(2,`text: ${oe(f.rawSize)} / ${oe(f.bestSize)} compressed`),o(2,`other: ${oe(p.rawSize)} / ${oe(p.bestSize)} compressed`),n+=f.created+p.created}o(2,`${((e,t,o=t+"s")=>1===e?`1 ${t}`:`${e} ${o}`)(n,"compressed file")} written`)})(t.servers,t.minCompress,se),t.noServe?i():n.set(t.servers,t.backgroundTasks).catch(e=>{if(e instanceof AggregateError)for(const t of e.errors)se(0,t instanceof Error?t.message:String(t));else se(0,e instanceof Error?e.message:String(e));process.stdin.destroy(),process.exit(1)})}function a(){return se(2,"refreshing config"),c()}c(),process.on("SIGHUP",()=>a()),process.stdin.on("data",e=>{e.includes("\n")&&a()});let f=!1;process.on("SIGINT",()=>{f||(f=!0,se(2,""),i())})})();