web-listener 1.3.0 → 1.3.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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-listener",
3
- "version": "1.3.0",
3
+ "version": "1.3.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 --experimental-import-meta-resolve
2
- import{readFile as e,stat as t,realpath as n,readdir as o,access as r,constants as s}from"node:fs/promises";import{sep as i,join as a,resolve as c,dirname as f,extname as u,basename as p}from"node:path";import{spawn as l,spawnSync as d}from"node:child_process";import{fileURLToPath as w,pathToFileURL as m}from"node:url";import{readZip as h,Queue as g,Router as y,fileServer as b,staticContent as $,staticFileFinder as v,dynamicFileFinder as j,Negotiator as x,negotiateEncoding as k,zipFileFinder as S,Property as E,requestHandler as O,addTeardown as _,getAbsolutePath as N,CONTINUE as M,proxy as z,assetServer as J,getSearch as P,getQuery as C,getPathParameter as R,WebListener as T,findCause as A,HTTPError as B,stringPredicate as I,compressFilesInDir as q,readMimeTypes as H,decompressMime as U,resetMime as D,registerMime as G}from"./index.js";import{text as L}from"node:stream/consumers";import{createServer as F}from"node:http";import{registerHooks as Z}from"node:module";import{openSync as K,constants as W,readSync as Q,closeSync as V}from"node:fs";import{inflateRawSync as X}from"node:zlib";const Y=Array.isArray,ee=[];function te(e){for(const t of ee)if(e.startsWith(t.path)&&(e.length===t.path.length||e[t.path.length]===i)){const n=e.substring(t.path.length+1);return{...t,remaining:n?n.split(i):[]}}}async function ne(e,n){const o=te(e);if(o)return o;const r=e.split(i);""===r[r.length-1]&&r.pop(),r[0]||(r.shift(),r.length>0&&(r[0]=i+r[0]));for(let e=r.length;e>0;--e){const o=a(...r.slice(0,e)),s=await t(o).catch(()=>null);if(!s)continue;if(!s.isFile())break;if(n&&e===r.length)return;const i={path:o,root:await h(o)};return ee.push(i),{...i,remaining:r.slice(e)}}}async function oe(t){const n=await ne(t,!0);if(n){const e=n.root.find(n.remaining);if(!e||e.isDirectory)throw new Error(`/${n.remaining.join("/")} not found in ${n.path}`);{const t=await e.open();try{return await L(t.createReadStream())}finally{await t.close()}}}return e(t,"utf-8")}function re(e){const t=te(e);return t?.root.find(t.remaining)}function se(){ee.length=0}const ie=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"]]),ae=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}],["dependencies",{type:"string"}],["mime",{type:"string",multi:!0}],["mime-types",{type:"string",multi:!0}],["redirect-map",{type:"string",multi:!0}],["write-compressed",{type:"boolean"}],["min-compress",{type:"number"}],["no-cache",{type:"boolean"}],["no-serve",{type:"boolean"}],["log",{type:"string"}],["log-format",{type:"string"}],["help",{type:"boolean"}],["version",{type:"boolean"}]]);function ce(e,t){const n=e=>{for(const o of e)t(o),"nested"===o.type&&n(o.mount)};for(const t of e)n(t.mount)}async function fe(e,t,n){const o=new Map,r=async(e,t)=>{const n=await s(t.file,null);return t.includeMime&&(e.mime=[...de(e.mime),...de(n.mime)],n.mime=[]),t.includeBackgroundTasks&&(e.backgroundTasks.push(...n.backgroundTasks),n.backgroundTasks=[]),void 0!==t.serverPort?n.servers.filter(e=>e.port===t.serverPort):void 0!==t.serverIndex?t.serverIndex<0||t.serverIndex>=n.servers.length?[]:[n.servers[t.serverIndex]]:n.servers};async function s(e,t){const s=o.get(e);if(s){if(!0===s)throw new Error(`circular reference to ${e}`);return s}o.set(e,!0),null===t&&(t=await oe(e));const i=n(JSON.parse(t),{file:e,path:""});for(let e=0;e<i.servers.length;){const t=i.servers[e];if(t.mount){for(let e=0;e<t.mount.length;++e){const n=t.mount[e];if("delegate"===n.type){const o=await r(i,n.config);if(1!==o.length)throw new Error(`${o.length>1?"multiple":"no"} servers found in ${n.config.file} matching requirements`);t.mount[e]={...n,type:"nested",mount:o[0].mount}}}++e}else{const n=await r(i,t);if(!n)throw new Error(`no servers found in ${t.file} matching requirements`);i.servers.splice(e,1,...n),e+=n.length}}const a=i;return o.set(e,a),a}return s(c(t||"."),e)}function ue(e,t){const n=new Map(Object.entries(e??{}).map(([e,t])=>[e,Array.isArray(t)?[...t]:[String(t)]]));for(const[e,o=""]of t){const t=n.get(e);t?t.push(o):n.set(e,[o])}return Object.fromEntries(n.entries())}function pe(e,t){const n=new Set(t.map(e=>e[0].toLowerCase()));return Object.fromEntries(Object.entries(e??{}).filter(e=>!n.has(e[0].toLowerCase())))}const le=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"}]]),de=e=>Y(e)?e:e?[e]:[];const we=e=>(t,n)=>{if(t!==e)throw new Ee(`expected ${JSON.stringify(e)}`,n,8);return e},me=e=>(t,n)=>{if(!e.has(t))throw new Ee(`expected one of ${JSON.stringify(e)}`,n,7);return t},he=e=>(t,n)=>{const o=[];let r=9;for(const s of e)try{return s(t,n)}catch(e){if(e instanceof Ee){if(e.p>r)continue;e.p!==r&&(r=e.p,o.length=0)}else r=-1;o.push(e)}throw 1===o.length?o[0]:new AggregateError(o)},ge=(e,t)=>{if(null!==e)throw new Ee("expected null, got "+typeof e,t);return null},ye=e=>(t,n)=>{if(!Y(t))throw new Ee("expected list, got "+typeof t,n);return t.map((t,o)=>e(t,{...n,path:`${n.path}[${o}]`}))},be=(e,t)=>{if("boolean"!=typeof e)throw new Ee("expected boolean, got "+typeof e,t);return e},$e=(e,t,n)=>(o,r)=>{if("number"!=typeof o)throw new Ee("expected number, got "+typeof o,r);if(e&&(0|o)!==o)throw new Ee(`expected integer, got ${o}`,r);if("number"==typeof t&&o<t)throw new Ee(`value cannot be less than ${t}`,r);if("number"==typeof n&&o>n)throw new Ee(`value cannot be greater than ${n}`,r);return o},ve=()=>(e,t)=>{if("string"!=typeof e)throw new Ee("expected string, got "+typeof e,t);try{return new RegExp(e)}catch{throw new Ee("expected regular expression",t)}},je=(e,t)=>(n,o)=>{if("string"!=typeof n)throw new Ee("expected string, got "+typeof n,o);if(e&&!e.test(n))throw new Ee(`expected string matching ${e}`,o);if("uri-reference"===t&&o.file){if(n.startsWith("file://"))return"file://"+c(f(o.file),n.substring(7));if(!n.includes("://"))return c(f(o.file),n)}return n},xe=(e,t,n)=>(o,r)=>{if("object"!=typeof o)throw new Ee("expected object, got "+typeof o,r);if(!o)throw new Ee("expected object, got null",r);if(Y(o))throw new Ee("expected object, got list",r);const s=[],i=new Set;for(const[n,a]of Object.entries(o)){i.add(n);const o=(e.get(n)??t)(a,{...r,path:`${r.path}.${n}`});void 0!==o&&s.push([n,o])}for(const e of n)if(!i.has(e))throw new Ee(`missing required property ${JSON.stringify(e)}`,r);for(const[t,n]of e)if(!i.has(t)){const e=n(void 0,{...r,path:`${r.path}.${t}`});void 0!==e&&s.push([t,e])}return Object.fromEntries(s)},ke=e=>e,Se=(e,t)=>{throw new Ee("unknown property",t)};class Ee extends Error{constructor(e,t,n=0){super(`${e} at ${t.path||"root"}`),this.p=n}}const Oe=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0;class _e extends Error{}const Ne=(e,t)=>ze(e.imports??{},t),Me=e=>e?e.startsWith("./")?e:"./"+e:void 0;function ze(e,t){const n=new Map;for(const[o,r]of Object.entries(e)){const e=Je(r,t);void 0!==e&&n.set(o,e)}return n}function Je(e,t){if("string"==typeof e||null===e)return e;if(Y(e))for(const n of e){const e=Je(n,t);if(e)return e}else if(e)for(const[n,o]of Object.entries(e))if("default"===n||t.has(n)){const e=Je(o,t);if(e)return e}}const Pe=async(t,n=f(t))=>({isRoot:!1,id:t,dir:n,packageJson:JSON.parse(await e(t,{encoding:"utf-8"})),dependencies:new Map,getFilePaths:()=>(async e=>{const t=new Set,n=new g({t:e,o:"."});for(const e of n)for(const r of await o(e.t,{withFileTypes:!0,encoding:"utf-8"}))r.name.startsWith(".")||(r.isFile()?t.add(`${e.o}/${r.name}`):r.isDirectory()&&"node_modules"!==r.name&&n.push({t:a(e.t,r.name),o:`${e.o}/${r.name}`}));return t})(n)});async function Ce(e,t){const o=t&&m(t);try{return await Pe(w(import.meta.resolve(e+"/package.json",o)))}catch{}try{const t=await(async e=>{const t=new Set(["",i]);for(let o=await n(e,{encoding:"utf-8"});!t.has(o);o=f(o)){t.add(o);const e=a(o,"package.json");try{return await r(e,s.R_OK),await Pe(e)}catch{}}throw new Error(`package.json not found in ${e} or any parent folder`)})(f(w(import.meta.resolve(e,o))));if(t.packageJson.name===e)return t}catch{}return null}function Re(e,t,n,o){for(const[r,s]of t)e.set(n+r.substring(1),s?o+qe(s.substring(1)):null)}function Te(e){return Object.fromEntries([...e.entries()].filter(([e,t])=>null!==t).map(([e,t])=>[qe(e),t]))}async function Ae(e,t,n,o){const r=new Map,s=[],i=[];for(const[t,a]of e){const e=t.indexOf("*"),c=a?.indexOf("*")??-1;if(!n(t)||null!==a&&(!o(a)||-1!==c!=(-1!==e)))throw new Error(`invalid entry: ${JSON.stringify(t)} => ${JSON.stringify(a)}`);if(-1!==e){const n=[t.length-1,t.substring(0,e),t.substring(e+1)],o=[e,t.length];a?s.push({i:n,u:[a.length-1,a.substring(0,c),a.substring(c+1)],l:o}):i.push({i:n,l:o})}else r.set(t,{u:a,l:[Number.POSITIVE_INFINITY,0]})}if(s.length){const e=await t();for(const t of e)for(const e of s){const n=Be(e.u,t);if(!1!==n){const o=e.i[1]+n+e.i[2],s=r.get(o);(!s||Ie(e.l,s.l)>0)&&r.set(o,{u:t,l:e.l})}}}for(const e of i)for(const[t,n]of r)null!==n.u&&Ie(e.l,n.l)>0&&!1!==Be(e.i,t)&&(n.u=null);return[...r].map(([e,t])=>[e,t.u])}const Be=(e,t)=>!!(t.length>=e[0]&&t.startsWith(e[1])&&t.endsWith(e[2]))&&t.substring(e[1].length,t.length-e[2].length),Ie=(e,t)=>e[0]-t[0]||e[1]-t[1],qe=e=>encodeURIComponent(e).replaceAll(/%2f/gi,"/").replaceAll(/%40/g,"@").replaceAll(/%23/g,"#");async function He(e,{environment:o=["browser","import","production"],mapFilePath:r="/importmap.json",sourcesBasePath:s="",modulesBasePath:i="/node_modules",...c}){const f=new y,u=await(async(e,t,n,o,r)=>{n.endsWith("/")&&(n=n.substring(0,n.length-1)),o.endsWith("/")&&(o=o.substring(0,o.length-1));const s=new Set,i=new Map,a=e.map(e=>{const t=r(e.packageJson,e.dir);let a=t;for(let e=2;s.has(a);++e)a=`${t}_${e}`;s.add(a);const c={m:e,h:[],$:e.isRoot?o:`${n}/${qe(a)}`,v:e.isRoot?null:a};return i.set(e.id,c),c});await Promise.all(a.map(async e=>{e.h=await Ae(((e,t)=>ze((e=>{const t=e.exports;if(void 0===t)return{".":{browser:Me(e.browser),module:Me(e.module),default:Me(e.main)??"./index.js"},"./*":"./*"};if(Y(t)||"object"!=typeof t||!t)return{".":t};for(const e of Object.keys(t))if(e.startsWith("."))return t;return{".":t}})(e),t))(e.m.packageJson,t),async()=>e.j??=await e.m.getFilePaths(),e=>"."===e||e.startsWith("./"),e=>e.startsWith("./"))}));const c=[];let f=new Map;const u=[];for(const e of a){const n=new Map;e.m.packageJson.name&&e.m.packageJson.exports&&Re(n,e.h,e.m.packageJson.name,e.$);for(const[t,o]of e.m.dependencies){const e=i.get(o);if(!e)throw new Error("internal reference mismatch");Re(n,e.h,t,e.$)}const o=await Ae(Ne(e.m.packageJson,t),async()=>(e.j??=await e.m.getFilePaths(),new Set([...n.keys(),...e.j])),e=>e.startsWith("#")&&e.length>1,e=>e.startsWith("./")||!e.startsWith("."));for(const[t,r]of o){if(!r){n.set(t,null);continue}const o=n.get(r);if(void 0!==o)n.set(t,o);else{if(!r.startsWith("./"))throw new Error(`unable to resolve import ${r}`);n.set(t,e.$+qe(r.substring(1)))}}e.m.isRoot?f=n:u.push([e.$+"/",n]),c.push({dir:e.m.dir,subPath:e.v,imports:n})}const p=[];for(const[e,t]of u){const n=new Map(t);for(const[e,t]of f)n.get(e)===t&&n.delete(e);n.size&&p.push([e,Te(n)])}return{packages:c,importMap:{imports:Te(f),scopes:Object.fromEntries(p)}}})(await(async e=>{const o=await t(e);if(o.isDirectory())e=a(e,"package.json");else if(!o.isFile())throw new Error(`invalid package.json path: ${e}`);const r=await Pe(await n(e));r.isRoot=!0;const s=new Set([r.id]),i=[r];for(const e of i){const t=new Map;for(const n of Object.keys(e.packageJson.peerDependencies??{}))t.set(n,!1);for(const[n,o]of Object.entries(e.packageJson.peerDependenciesMeta??{}))t.has(n)&&t.set(n,o.optional??!1);for(const n of Object.keys(e.packageJson.optionalDependencies??{}))t.set(n,!0);for(const n of Object.keys(e.packageJson.dependencies??{}))t.set(n,!1);const n=a(e.dir,"a.js");await Promise.all([...t].map(async([t,o])=>{const r=await Ce(t,n);if(!r){if(o)return;throw new Error(`package ${t} not found (required by ${e.dir})`)}s.has(r.id)||(s.add(r.id),i.push(r)),e.dependencies.set(t,r.id)}))}return i})(e),new Set(o),i,s,Ue);for(const{dir:e,subPath:t}of u.packages)t&&f.mount("/"+t,await b(e,{...c,hide:[...c.hide??[],"node_modules"],indexFiles:[]}));const p=JSON.stringify(u.importMap);if(r){const e={headers:c.headers,encodings:["br","gzip"],minCompression:100};f.get(r,$(Buffer.from(p,"utf-8"),"application/importmap+json",e)),f.get(r+".js",$(Buffer.from(`const s=document.createElement('script');s.type='importmap';s.textContent=JSON.stringify(${p});document.head.append(s);`,"utf-8"),"text/javascript; charset=utf-8",e))}return Object.assign(f,{importMapJSON:p})}const Ue=e=>(e.name||"-")+(e.version?`@${e.version}`:""),De=new Map([[".mjs","module"],[".cjs","commonjs"],[".mts","module-typescript"],[".cts","commonjs-typescript"],[".json","json"],[".wasm","wasm"]]);Z({resolve(e,t,n){if("web-listener"===e)e=a(f(w(import.meta.url)),"index.js");else{const n=URL.parse(e,t.parentURL);if(n&&"file:"===n.protocol&&re(w(n)))return{url:n.toString(),format:De.get(u(n.pathname)),shortCircuit:!0}}return n(e,t)},load(e,t,n){if(e.startsWith("file://")){const t=w(e),n=re(t);if(n&&!n.isDirectory)return{source:(e=>{const t=e.meta();if(!t.s)return Buffer.alloc(0);const n=Buffer.alloc(t.s),o=K(e.zipFilePath,W.O_RDONLY);try{if(Q(o,n,0,t.s,t.p)!==t.s)throw new Error("zip content has changed");return t.z?X(n,{maxOutputLength:e.stat().size}):n}finally{V(o)}})(n),format:De.get(u(t))??"commonjs",shortCircuit:!0}}return n(e,t)}});const Ge=new Set,Le=new Set;async function Fe(e,t,n){Le.has(e)||(Ge.has(e)&&n(`${e} has already been loaded into the module cache and will not be updated`),Le.add(e)),await ne(e,!0);const o=await import(e),r=o?.[t||"default"];if(!r||"function"!=typeof r&&"object"!=typeof r)throw new Error(`${e} must export a request handler ${t?`named ${JSON.stringify(t)}`:"as default"}`);return r}async function Ze(e,n){const o=await t(e).catch(()=>null);if(o?.isDirectory())return"static-paths"===n.mode?v(e,n):j(e,n);const r=await ne(e,!1);if(!r)throw new _e(`content to serve not found at ${e}`);const s=r.root.find(r.remaining);if(!s?.isDirectory)throw new Error(`/${r.remaining.join("/")} in ${r.path} is not a directory`);const i=n;return i.negotiator||(i.negotiator=new x([k(["gzip"])])),S(s,i)}class Ke{constructor(e){this.k=e?e=>e:e=>e.toLowerCase(),this.S=new Map,this.O=[],this._=""}add(e,t){if("/"===e[0])this.S.set(this.k(e),t);else{if("~"!==e[0])throw new Error(`invalid URL: ${e}`);{const n="*"!==e[1],o=e.substring(n?1:2);this.O.push({N:new RegExp(o,n?"":"i"),u:t})}}}setDefault(e){this._=e}get(e){const t=this.S.get(this.k(e));if(void 0!==t)return t;for(const t of this.O){t.N.lastIndex=0;const n=t.N.exec(e);if(n)return t.u.replaceAll(/\$(?:([0-9]+)|([a-zA-Z][a-zA-Z0-9]*))/g,(e,t,o)=>t?n[Number.parseInt(t)]??"":n.groups&&Object.prototype.hasOwnProperty.call(n.groups,o)?n.groups[o]??"":"")}return this._}}function*We(e){let t=[];const n=/(\s+|#[^\n]*)|(;)|(?:"((?:[^"\\]+|\\.)*)")|(?:'((?:[^'\\]+|\\.)*)')|((?:[^#;\s\\"']+|\\.)+)/y;for(;n.lastIndex<e.length;){const o=n.exec(e);if(!o)throw new Error("invalid nginx syntax");const[,r,s,i,a,c]=o;if(!r)if(s)yield t,t=[];else if(c&&!c.includes("\\"))t.push({token:c,literal:!0});else{const e=i??a??c;if(void 0===e)throw new Error("nginx tokenisation error");t.push({token:e.replaceAll(/\\(.)/g,"$1"),literal:!1})}}if(t.length)throw new Error("unterminated statement - ensure all statements end with a semicolon")}const Qe=(e,t,n="raw")=>e.replaceAll(/\$\{(?:(raw|html|json|int|uri)\()?([^${}():]+)(?:(\))?:-((?:[^})\\]|\\.)*))?(\))?\}/g,(e,o,r,s,i,a)=>{if((s?1:0)+(a?1:0)!=(o?1:0))return e;const c=t(r),f=Ve[o??n];let u=c.M;return s&&u&&f&&(u=f(u)),u??=i?.replaceAll(/\\(.)/g,"$1")??"",a&&f&&(u=f(u)),!o&&f&&c.J!==n&&(u=f(u)),Xe(u)}),Ve={html:e=>Xe(e).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;"),json:e=>JSON.stringify(Xe(e)),int:e=>{if(Y(e))return"0";const t=/^(?:\+|(-))?0*(\d+)$/.exec(e);return t?(t[1]??"")+t[2]:"0"},uri:e=>Y(e)?e.map(encodeURIComponent):encodeURIComponent(e)},Xe=e=>Y(e)?e.join("/"):e,Ye=new E(0),et=e=>(Ye.set(e,e.url?.length??0),M);async function tt(e,t=()=>{},n){const o=new y;n&&o.use(O((e,t)=>{const o=Date.now();return _(e,()=>{const r=Date.now()-o;let s=N(e);const i=Ye.get(e);if(i>1){const e=s.length+1-i;s=s.substring(0,e)+("?"===s[e]?"?":"")+"[***]"}n({method:e.method??"GET",path:s,status:t.statusCode,duration:r})}),M}));for(const n of e)switch(n.type){case"nested":o.mount(n.path,n.maskSubpaths?et:null,await tt(n.mount,t));break;case"headers":{const e=new Map(Object.entries(n.headers));o.mount(n.path,(t,n)=>(n.setHeaders(e),M));break}case"files":if("/dev/null"!==n.dir){const e=n.options.negotiation&&n.options.negotiation.length>0?new x(n.options.negotiation):void 0;o.mount(n.path,J(await Ze(n.dir,{...n.options,negotiator:e}),n.options))}break;case"proxy":o.mount(n.path,z(n.target,{...n.options,responseHeaders:[(e,t,o)=>({...o,...n.options.headers})]}));break;case"fixture":{const e=(e,t)=>{for(const[o,r]of Object.entries(n.headers))"string"==typeof r?t.setHeader(o,Qe(r,nt(e))):"number"==typeof r?t.setHeader(o,r):t.setHeader(o,r.map(t=>Qe(t,nt(e))));t.statusCode=n.status,t.end(Qe(n.body,nt(e)))};"GET"===n.method&&o.onRequest("HEAD",n.path,e),o.onRequest(n.method,n.path,e);break}case"redirect":o.at(n.path,O((e,t)=>{let o=Qe(n.target,nt(e),"uri");"/"===n.target[0]&&(o=o.replace(/^\/{2,}/,"/")),t.setHeader("location",o),t.statusCode=n.status,t.end()}));break;case"redirect-map":{const e=new Ke(n.options.caseSensitive);if("string"==typeof n.mapping){let t;try{t=await oe(n.mapping)}catch(e){throw e&&"object"==typeof e&&"code"in e&&"ENOENT"===e.code?new _e(`redirect-map file not found at ${n.mapping}`):e}e:for(const n of We(t)){const t=n[0];if(t.literal)switch(t.token){case"default":if(2===n.length){e.setDefault(n[1].token);continue e}break;case"hostnames":throw new Error("redirect-map does not support hostnames");case"include":throw new Error("redirect-map does not support nested mapping files")}if(2!==n.length)throw new Error(`unknown statement in mapping file: ${n.map(e=>JSON.stringify(e)).join(" ")}`);e.add(t.token,n[1].token)}}else for(const[t,o]of Object.entries(n.mapping))e.add(t,o);o.use((t,o)=>{const r=e.get(t.url??"/");return r&&r!==(t.url??"/")?(o.setHeader("location",r),o.statusCode=n.status,o.end()):M});break}case"dependencies":o.mount(n.path,await He(n.package,{...n.options,modulesBasePath:n.path}));break;case"custom":{const e=await Fe(n.import,n.namedExport,t);"string"==typeof n.method&&"get"===n.method.toLowerCase()?o.get(n.path,e):n.method?o.onRequest(n.method,n.path,e):o.mount(n.path,n.maskSubpaths?et:null,e);break}}return o}const nt=e=>t=>"?"===t?{M:P(e)||void 0,J:"uri"}:"?"===t[0]?{M:C(e,t.substring(1)),J:"raw"}:{M:R(e,t),J:"raw"};class ot{constructor(){this.P=!1,this.C=!1,this.R=!1,this.T=new Map,this.A=new Set}async set(e,t,n,o){if(!this.C&&!this.R){clearTimeout(this.B),this.B=void 0;try{this.C=!0;const r=[],s=[];let i=!1;const a=this.A;this.A=new Set;e:for(let e=0;e<t.length;++e){const o=t[e];for(const e of a)if(it(e.config,o)){i||=e.isRunning(),this.A.add(e),a.delete(e);continue e}i=!0,s.push(async()=>{this.A.add(await this.I(o,n))})}for(const e of a)r.push(e.close);const c=new Set;for(let t=0;t<e.length;++t){const o=e[t],r=o.port;r<=0||r>65535?n(0,{type:"warn",message:`servers[${t}] must have a specific port from 1 to 65535`}):c.has(r)?n(0,{type:"warn",message:`skipping servers[${t}] because port ${r} has already been defined`}):(c.add(r),s.push(async()=>{const e=await this.q(o,this.T.get(r),n);e?this.T.set(r,e):this.T.delete(r)}))}this.P||=s.length>0;for(const[e,t]of this.T)c.has(e)||(s.push(t.close),this.T.delete(e));let f=!1;const u=async e=>{let t=[];if(await Promise.all(e.map(async e=>{try{await e()}catch(e){i&&!this.R&&e instanceof _e?(n(1,{type:"warn",message:`${e.message} (retrying)`}),f=!0):t.push(e)}})),t.length)throw await this.H(n),t.length>1?new AggregateError(t):t[0]};await u(r),await u(s),(()=>{for(const e of Le)Ge.add(e);Le.clear()})(),this.R?this.H(n):f?this.B=setTimeout(()=>{se(),this.set(e,t,n,o)},1e3):this.T.size?n(1,{message:"all servers ready"}):n(1,{message:"no servers configured"})}catch(e){o(e)}finally{this.C=!1}}}async I(e,t){const n={service:e.command,serviceCol:"35"},o=new AbortController;return new Promise((r,s)=>{t(2,{...n,type:"detail",message:"starting"});let i=!0;const a=l(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?rt(a.stdout,e=>t(0,{...n,thread:"stdout",message:e})):a.stdout.resume(),e.options.displayStderr?rt(a.stderr,e=>t(0,{...n,thread:"stderr",message:e})):a.stderr.resume(),a.addListener("error",e=>{t(0,{...n,type:"error",message:`startup failed: ${e.message}`}),i=!1,s(e)}),a.addListener("exit",(e,o)=>{t(2,{...n,message:"closed",stats:null!==e?{code:e}:{signal:o}}),i=!1}),a.addListener("spawn",()=>r({config:e,isRunning:()=>i,close:()=>new Promise(o=>{null!==a.signalCode||null!==a.exitCode?o():(t(2,{...n,type:"detail",message:"closing",stats:{signal:e.options.killSignal}}),a.addListener("exit",o),a.kill(e.options.killSignal))})}))})}async q({port:e,host:t,options:n,mount:o},r,s){const i={service:`http://${t}:${e}`,serviceCol:"34"},a=await tt(o,e=>s(1,{...i,type:"warn",message:e}),n.logRequests?e=>s(0,{...i,method:e.method,path:e.path,status:e.status,stats:{duration:e.duration}}):void 0),c=new T(a);let f,u;if(c.addEventListener("error",e=>{e.preventDefault();const{error:t,context:n,request:o}=e.detail;(A(t,B)?.statusCode??500)>=500&&s(0,{...i,thread:n,type:"error",path:o?.url,message:String(t)})}),r&&t===r.host&&((e,t)=>{for(const n of at)if(e[n]!==t[n])return!1;return!0})(n,r.options))f=r.server,s(2,{...i,message:"updated"}),r.detach();else{if(r?(s(2,{...i,type:"detail",message:"restarting (step 1: shutdown)"}),await r.close(),s(2,{...i,type:"detail",message:"restarting (step 2: start)"})):s(2,{...i,type:"detail",message:"starting"}),this.R)return;f=F(n),f.setTimeout(n.socketTimeout),u=st(f,e,t,n.backlog)}const p=c.attach(f,n);return u&&(await u,await new Promise(e=>setTimeout(e,1)),s(2,{...i,message:"ready"})),{host:t,options:n,server:f,detach:()=>p("restart",n.restartTimeout),close:()=>new Promise(e=>{const t=p("shutdown",n.shutdownTimeout,!0,()=>{f.close(()=>{s(2,{...i,message:"closed"}),e()}),f.closeAllConnections()}).countConnections();t>0&&s(2,{...i,type:"detail",message:"closing",stats:{connections:t}})})}}async H(e){this.T.size&&(e(2,{type:"detail",message:"shutting down"}),await Promise.all([...this.T.values(),...this.A].map(e=>e.close()))),this.P&&e(2,{message:"shutdown complete"})}shutdown(e){this.R||(clearTimeout(this.B),this.B=void 0,this.R=!0,this.C||this.H(e))}}function rt(e,t){let n="";const o=e=>t(e.replaceAll(/\x1b\[[\d;]*[A-K]/g,"").replaceAll(/[\x00-\x1f]/g,e=>`\\x${e.charCodeAt(0).toString(16).padStart(2,"0")}`));e.addListener("data",e=>{n+=e;const t=n.split("\n");n=t.pop();for(const e of t)o(e)}),e.addListener("end",()=>{n&&o(n)})}const st=async(e,t,n,o=511)=>new Promise((r,s)=>{e.once("error",s),e.listen(t,n,o,()=>{e.off("error",s),r()})});function it(e,t){return e.command===t.command&&e.arguments.length===t.arguments.length&&e.arguments.every((e,n)=>e===t.arguments[n])&&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 at=["requestTimeout","keepAliveTimeout","keepAliveTimeoutBuffer","connectionsCheckingInterval","headersTimeout","highWaterMark","maxHeaderSize","noDelay","requireHostHeader","keepAlive","keepAliveInitialDelay","uniqueHeaders","backlog","socketTimeout"];function ct(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 ft=(e,t,n=t+"s")=>1===e?`1 ${t}`:`${e} ${n}`,ut=e=>e<2e3?`${e}B`.padStart(8," "):`${(e/1024).toFixed(1)}kB`.padStart(8," "),pt=["none","ready","progress"],lt=(e,t)=>{const n=e.isTTY&&!process.env.NO_COLOR?(e,t)=>e?`\x1b[${e}m${t}\x1b[0m`:t:(e,t)=>t,o=pt.indexOf(t);return(t,r)=>{if(t>o)return;const s=[];if(r.service&&(r.serviceCol?s.push(n(r.serviceCol,r.service)):s.push(r.service)),r.thread&&s.push(n("2",`[${r.thread}]`)),"warn"===r.type?s.push(n("33","warning")+":"):"error"===r.type&&s.push(n("91","error")+":"),void 0!==r.method&&s.push(n("1",r.method.replaceAll(/[^a-zA-Z0-9\-_]/g,"?")||"?")),void 0!==r.path&&s.push(r.path),void 0!==r.status&&s.push(n(dt[r.status/100|0]??"",String(r.status))),r.message){const e=r.message instanceof Error?r.message.message:String(r.message);"detail"===r.type?s.push(n("2",e)):s.push(e)}r.stats&&s.push(n("2",`(${Object.entries(r.stats).map(([e,t])=>"duration"===e?`${t}ms`:`${e}=${t}`).join("; ")})`)),e.write(`${s.join(" ")}\n`)}},dt=["","37","32","36","31","41;97"];process.on("SIGUSR1",()=>{});const wt=process.stderr;let mt=lt(wt,"progress");function ht(e){process.stdin.destroy(),mt(0,{type:"error",message:e})}process.on("unhandledRejection",ht),process.on("uncaughtException",ht);const gt=(e=>{const t=[];for(let n=0;n<e.length;++n){const o=e[n];if("--"===o)continue;const r=/^--([^ =\-][^ =]*)=(.*)$/.exec(o);if(r){t.push([r[1],r[2]]);continue}const s=/^-([^ =]*)([^ =])=(.*)$/.exec(o);if(s&&"-"!==o[1]){for(const e of s[1])t.push(["-"+e,""]);t.push(["-"+s[2],s[3]]);continue}if("-"!==o[0]||"-"===o){if(0!==n)throw new Error(`value without key: ${o}`);t.push(["",o]);continue}let i=e[n+1];if(i&&"-"===i[0]&&i.length>1&&(i=void 0),void 0!==i&&++n,"-"===o[1])t.push([o.slice(2),i??""]);else{for(const e of o.slice(1,o.length-1))t.push(["-"+e,""]);t.push(["-"+o[o.length-1],i??""])}}const n=new Map;for(const[e,o]of t){const t=(ie.get(e)??e).toLowerCase(),r=ae.get(t);if(!r)throw new Error(`unknown flag: ${e}`);let s;switch(r.type){case"string":s=o;break;case"number":s=Number.parseFloat(o);break;case"boolean":s=["","on","true","yes","y","1"].includes(o.toLowerCase())}if(r.multi){let e=n.get(t);e||(e=[],n.set(t,e)),e.push(s)}else{if(n.has(t))throw new Error(`multiple values for ${t}`);n.set(t,s)}}return n})(process.argv.slice(2)),yt=f(w(import.meta.url));if(gt.get("version")||gt.get("help")){let t={name:"web-listener",version:"unknown"};try{t=JSON.parse(await e(a(yt,"package.json"),"utf-8"))}catch{}gt.get("help")?d("man",["-M",yt,t.name],{stdio:["inherit","inherit","inherit"]}):process.stdout.write(`${t.name} ${t.version}\n`),process.exit(0)}(async()=>{const t=new ot;process.on("unhandledRejection",()=>t.shutdown(mt)),process.on("uncaughtException",()=>t.shutdown(mt));const n=function(e){const t=n=>{const o=n.$ref;if(o&&o.startsWith("#/$defs/")){const t=Oe(e.$defs,o.substring(8));if(!t)throw new Error(`unable to find ${o} in schema`);n={...t,...n}}const r=((e,t)=>{if(void 0!==e.const)return we(e.const);if(e.enum)return me(new Set(e.enum));if(e.anyOf)return he(e.anyOf.map(t));switch(e.type){case"null":return ge;case"array":return ye(t(e.items));case"boolean":return be;case"integer":return $e(!0,e.minimum,e.maximum);case"number":return $e(!1,e.minimum,e.maximum);case"object":const n=e.additionalProperties??!0,o=xe(new Map(Object.entries(e.properties??{}).map(([e,n])=>[e,t(n)])),"object"==typeof n?t(n):n?ke:Se,e.required??[]);if(e.$comment?.startsWith("flatten:")){const t=e.$comment.substring(8);return(e,n)=>o(e,n)[t]}return o;case"string":if("regex"===e.format)return ve();let r=null;return e.pattern&&(r=new RegExp(e.pattern)),je(r,e.format??"");default:throw new Error(`unknown part type ${JSON.stringify(e)}`)}})(n,t);if(void 0!==n.default){const e=JSON.stringify(n.default);return(t,n)=>r(void 0===t?JSON.parse(e):t,n)}return(e,t)=>void 0===e?void 0:r(e,t)};return t(e)}(await(async()=>JSON.parse(await e(a(f(w(import.meta.url)),"schema.json"),"utf-8")))());function o(){process.stdin.destroy(),t.shutdown(mt)}async function r(){se();const e=await(async(e,t)=>{const n=(e,n=[])=>t.get(e)??n,o=e=>t.get(e),r=e=>t.get(e),s=o("config-file"),i=o("config-json"),a=r("port"),c=o("host"),f=n("dir"),u=n("exec").map(e=>e.split(" ")),p=n("ext").map(e=>e.startsWith(".")?e:`.${e}`),l=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=o("404"),w=o("spa"),m=o("proxy"),h=o("dependencies"),g=r("min-compress"),y=n("mime"),b=n("mime-types"),$=n("redirect-map"),v=o("log"),j=o("log-format");if(Number(Boolean(s))+Number(Boolean(i))+Number(Boolean(m))>1)throw new Error("multiple config files are not supported");let x;s?x=await fe(null,s,e):i?x=await fe(i,"",e):(f.length||f.push("."),x=e({servers:[{port:8080,mount:[]}]},{file:"",path:""}));for(const e of u)x.backgroundTasks.push({command:e[0],arguments:e.slice(1),cwd:process.cwd(),environment:{},options:{killSignal:"SIGTERM",displayStdout:!0,displayStderr:!0}});const k=1===x.servers.length?x.servers[0]:void 0;if(void 0!==a){if((0|a)!==a)throw new Error("port must be an integer");if(!k)throw new Error("cannot specify port on commandline when defining multiple servers");k.port=a}if(void 0!==c)for(const e of x.servers)e.host=c;if(f.length||w||d){if(!k)throw new Error("cannot specify dir, spa, or 404 on commandline when defining multiple servers");let e;w?e={statusCode:200,filePath:w}:d&&(e={statusCode:404,filePath:d});for(let t=0;t<f.length;++t){const n=f[t],o=t===f.length-1;k.mount.push({type:"files",path:"/",dir:n,options:{fallback:o?e:void 0}})}}if(m){if(!k)throw new Error("cannot specify proxy on commandline when defining multiple servers");k.mount.push({type:"proxy",path:"/",target:m,options:{}})}if($.length>0)for(const e of x.servers)for(const t of $)e.mount.unshift({type:"redirect-map",mapping:t,status:307,options:{caseSensitive:!1}});if(void 0!==h)for(const e of x.servers)e.mount.push({type:"dependencies",path:"/node_modules",package:h,options:{}});const S=(e,t)=>ce(x.servers,n=>{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,n]of le)t.get(e)&&S("encoding",n);if(p.length&&ce(x.servers,e=>{"files"===e.type&&(e.options.implicitSuffixes=p)}),t.get("no-cache")&&l.push(["cache-control","no-store, no-cache, max-age=0"]),l.length){const e={type:"headers",path:"/",headers:{}};ce(x.servers,e=>{switch(e.type){case"headers":case"fixture":e.headers=pe(e.headers,l);break;case"files":case"proxy":case"dependencies":e.options.headers=pe(e.options.headers,l)}});for(const t of x.servers){let n=t.mount[0];"headers"===n?.type&&"/"===n.path||(n={...e},t.mount.unshift(n)),n.headers=ue(n.headers,l)}}switch((y.length||b.length)&&(x.mime=[...de(x.mime),...y,...b.map(e=>`file://${e}`)]),t.get("write-compressed")&&(x.writeCompressed=!0),void 0!==g&&(x.minCompress=g),t.get("no-serve")&&(x.noServe=!0),v){case void 0:break;case"none":case"ready":case"progress":x.log=v;for(const e of x.servers)e.options.logRequests=!1;for(const e of x.backgroundTasks)e.options.displayStderr=!1,e.options.displayStdout=!1;break;case"full":x.log="progress";break;default:throw new Error(`unknown log level: ${v}`)}switch(j){case void 0:break;case"text":case"json":x.logFormat=j;break;default:throw new Error(`unknown log format: ${j}`)}return x})(n,gt);mt="json"===e.logFormat?((e,t)=>{const n=pt.indexOf(t);return(t,{serviceCol:o,message:r,...s})=>{t>n||(r instanceof Error?r=r.message:void 0!==r&&(r=String(r)),e.write(`${JSON.stringify({...s,message:r})}\n`))}})(wt,e.log):lt(wt,e.log),await(async e=>{const t=[];for(const n of Y(e)?e:[e])"string"!=typeof n?t.push(new Map(Object.entries(n))):n.startsWith("file://")?t.push(H(await oe(n.substring(7)))):t.push(U(n));D();for(const e of t)G(e)})(e.mime),e.writeCompressed&&await(async(e,t,n)=>{let o=0;for(const r of e)for(const e of r.mount)if("files"===e.type){const r=e.options.negotiation?.find(e=>"encoding"===e.feature);if(!r?.options?.length){n(2,{message:`skipping ${e.dir} because no compression is configured`});continue}const s=r.match?` matching ${r.match}`:"";n(2,{message:`compressing files in ${e.dir}${s} using ${r.options.map(e=>e.value).join(", ")}`});const i=I(r.match,!0),a=await q(e.dir,r.options,{...e.options,minCompression:t,filter:(e,t)=>!["image","video","audio","font"].includes(t.split("/")[0])&&i(p(e))}),c=ct(a.filter(({mime:e})=>e.startsWith("text/"))),f=ct(a.filter(({mime:e})=>!e.startsWith("text/")));n(2,{message:`text: ${ut(c.rawSize)} / ${ut(c.bestSize)} compressed`}),n(2,{message:`other: ${ut(f.rawSize)} / ${ut(f.bestSize)} compressed`}),o+=c.created+f.created}n(2,{message:`${ft(o,"compressed file")} written`})})(e.servers,e.minCompress,mt),e.noServe?o():t.set(e.servers,e.backgroundTasks,mt,e=>{if(e instanceof AggregateError)for(const t of e.errors)mt(0,{type:"error",message:t});else mt(0,{type:"error",message:e});process.stdin.destroy(),process.exit(1)})}function s(){return mt(2,{message:"refreshing config"}),r()}r(),process.on("SIGHUP",()=>s()),process.stdin.on("data",e=>{e.includes("\n")&&s()});let i=!1;process.on("SIGINT",()=>{i||(i=!0,wt.isTTY&&wt.write("\n"),o())})})();
2
+ import{readFile as e,stat as t,realpath as n,readdir as o,access as r,constants as s}from"node:fs/promises";import{sep as i,join as a,resolve as c,dirname as f,extname as u,basename as p}from"node:path";import{spawn as l,spawnSync as d}from"node:child_process";import{fileURLToPath as w,pathToFileURL as m}from"node:url";import{readZip as h,Queue as g,Router as y,fileServer as b,staticContent as $,staticFileFinder as v,dynamicFileFinder as j,Negotiator as x,negotiateEncoding as k,zipFileFinder as S,Property as E,requestHandler as O,addTeardown as _,getAbsolutePath as N,CONTINUE as M,proxy as z,assetServer as J,getSearch as P,getQuery as C,getPathParameter as R,WebListener as T,findCause as A,HTTPError as B,stringPredicate as I,compressFilesInDir as q,readMimeTypes as H,decompressMime as U,resetMime as D,registerMime as G}from"./index.js";import{text as L}from"node:stream/consumers";import{createServer as F}from"node:http";import{registerHooks as Z}from"node:module";import{openSync as K,constants as W,readSync as Q,closeSync as V}from"node:fs";import{inflateRawSync as X}from"node:zlib";const Y=Array.isArray,ee=[];function te(e){for(const t of ee)if(e.startsWith(t.path)&&(e.length===t.path.length||e[t.path.length]===i)){const n=e.substring(t.path.length+1);return{...t,remaining:n?n.split(i):[]}}}async function ne(e,n){const o=te(e);if(o)return o;const r=e.split(i);""===r[r.length-1]&&r.pop(),r[0]||(r.shift(),r.length>0&&(r[0]=i+r[0]));for(let e=r.length;e>0;--e){const o=a(...r.slice(0,e)),s=await t(o).catch(()=>null);if(!s)continue;if(!s.isFile())break;if(n&&e===r.length)return;const i={path:o,root:await h(o)};return ee.push(i),{...i,remaining:r.slice(e)}}}async function oe(t){const n=await ne(t,!0);if(n){const e=n.root.find(n.remaining);if(!e||e.isDirectory)throw new Error(`/${n.remaining.join("/")} not found in ${n.path}`);{const t=await e.open();try{return await L(t.createReadStream())}finally{await t.close()}}}return e(t,"utf-8")}function re(e){const t=te(e);return t?.root.find(t.remaining)}function se(){ee.length=0}const ie=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"]]),ae=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}],["dependencies",{type:"string"}],["mime",{type:"string",multi:!0}],["mime-types",{type:"string",multi:!0}],["redirect-map",{type:"string",multi:!0}],["write-compressed",{type:"boolean"}],["min-compress",{type:"number"}],["no-cache",{type:"boolean"}],["no-serve",{type:"boolean"}],["log",{type:"string"}],["log-format",{type:"string"}],["help",{type:"boolean"}],["version",{type:"boolean"}]]);function ce(e,t){const n=e=>{for(const o of e)t(o),"nested"===o.type&&n(o.mount)};for(const t of e)n(t.mount)}async function fe(e,t,n){const o=new Map,r=async(e,t)=>{const n=await s(t.file,null);return t.includeMime&&(e.mime=[...de(e.mime),...de(n.mime)],n.mime=[]),t.includeBackgroundTasks&&(e.backgroundTasks.push(...n.backgroundTasks),n.backgroundTasks=[]),void 0!==t.serverPort?n.servers.filter(e=>e.port===t.serverPort):void 0!==t.serverIndex?t.serverIndex<0||t.serverIndex>=n.servers.length?[]:[n.servers[t.serverIndex]]:n.servers};async function s(e,t){const s=o.get(e);if(s){if(!0===s)throw new Error(`circular reference to ${e}`);return s}o.set(e,!0),null===t&&(t=await oe(e));const i=n(JSON.parse(t),{file:e,path:""});for(let e=0;e<i.servers.length;){const t=i.servers[e];if(t.mount){for(let e=0;e<t.mount.length;++e){const n=t.mount[e];if("delegate"===n.type){const o=await r(i,n.config);if(1!==o.length)throw new Error(`${o.length>1?"multiple":"no"} servers found in ${n.config.file} matching requirements`);t.mount[e]={...n,type:"nested",mount:o[0].mount}}}++e}else{const n=await r(i,t);if(!n)throw new Error(`no servers found in ${t.file} matching requirements`);i.servers.splice(e,1,...n),e+=n.length}}const a=i;return o.set(e,a),a}return s(c(t||"."),e)}function ue(e,t){const n=new Map(Object.entries(e??{}).map(([e,t])=>[e,Array.isArray(t)?[...t]:[String(t)]]));for(const[e,o=""]of t){const t=n.get(e);t?t.push(o):n.set(e,[o])}return Object.fromEntries(n.entries())}function pe(e,t){const n=new Set(t.map(e=>e[0].toLowerCase()));return Object.fromEntries(Object.entries(e??{}).filter(e=>!n.has(e[0].toLowerCase())))}const le=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"}]]),de=e=>Y(e)?e:e?[e]:[];const we=e=>(t,n)=>{if(t!==e)throw new Ee(`expected ${JSON.stringify(e)}`,n,8);return e},me=e=>(t,n)=>{if(!e.has(t))throw new Ee(`expected one of ${JSON.stringify(e)}`,n,7);return t},he=e=>(t,n)=>{const o=[];let r=9;for(const s of e)try{return s(t,n)}catch(e){if(e instanceof Ee){if(e.p>r)continue;e.p!==r&&(r=e.p,o.length=0)}else r=-1;o.push(e)}throw 1===o.length?o[0]:new AggregateError(o)},ge=(e,t)=>{if(null!==e)throw new Ee("expected null, got "+typeof e,t);return null},ye=e=>(t,n)=>{if(!Y(t))throw new Ee("expected list, got "+typeof t,n);return t.map((t,o)=>e(t,{...n,path:`${n.path}[${o}]`}))},be=(e,t)=>{if("boolean"!=typeof e)throw new Ee("expected boolean, got "+typeof e,t);return e},$e=(e,t,n)=>(o,r)=>{if("number"!=typeof o)throw new Ee("expected number, got "+typeof o,r);if(e&&(0|o)!==o)throw new Ee(`expected integer, got ${o}`,r);if("number"==typeof t&&o<t)throw new Ee(`value cannot be less than ${t}`,r);if("number"==typeof n&&o>n)throw new Ee(`value cannot be greater than ${n}`,r);return o},ve=()=>(e,t)=>{if("string"!=typeof e)throw new Ee("expected string, got "+typeof e,t);try{return new RegExp(e)}catch{throw new Ee("expected regular expression",t)}},je=(e,t)=>(n,o)=>{if("string"!=typeof n)throw new Ee("expected string, got "+typeof n,o);if(e&&!e.test(n))throw new Ee(`expected string matching ${e}`,o);if("uri-reference"===t&&o.file){if(n.startsWith("file://"))return"file://"+c(f(o.file),n.substring(7));if(!n.includes("://"))return c(f(o.file),n)}return n},xe=(e,t,n)=>(o,r)=>{if("object"!=typeof o)throw new Ee("expected object, got "+typeof o,r);if(!o)throw new Ee("expected object, got null",r);if(Y(o))throw new Ee("expected object, got list",r);const s=[],i=new Set;for(const[n,a]of Object.entries(o)){i.add(n);const o=(e.get(n)??t)(a,{...r,path:`${r.path}.${n}`});void 0!==o&&s.push([n,o])}for(const e of n)if(!i.has(e))throw new Ee(`missing required property ${JSON.stringify(e)}`,r);for(const[t,n]of e)if(!i.has(t)){const e=n(void 0,{...r,path:`${r.path}.${t}`});void 0!==e&&s.push([t,e])}return Object.fromEntries(s)},ke=e=>e,Se=(e,t)=>{throw new Ee("unknown property",t)};class Ee extends Error{constructor(e,t,n=0){super(`${e} at ${t.path||"root"}`),this.p=n}}const Oe=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0;class _e extends Error{}const Ne=(e,t)=>ze(e.imports??{},t),Me=e=>e?e.startsWith("./")?e:"./"+e:void 0;function ze(e,t){const n=new Map;for(const[o,r]of Object.entries(e)){const e=Je(r,t);void 0!==e&&n.set(o,e)}return n}function Je(e,t){if("string"==typeof e||null===e)return e;if(Y(e))for(const n of e){const e=Je(n,t);if(e)return e}else if(e)for(const[n,o]of Object.entries(e))if("default"===n||t.has(n)){const e=Je(o,t);if(e)return e}}const Pe=async(t,n=f(t))=>({isRoot:!1,id:t,dir:n,packageJson:JSON.parse(await e(t,{encoding:"utf-8"})),dependencies:new Map,getFilePaths:()=>(async e=>{const t=new Set,n=new g({t:e,o:"."});for(const e of n)for(const r of await o(e.t,{withFileTypes:!0,encoding:"utf-8"}))r.name.startsWith(".")||(r.isFile()?t.add(`${e.o}/${r.name}`):r.isDirectory()&&"node_modules"!==r.name&&n.push({t:a(e.t,r.name),o:`${e.o}/${r.name}`}));return t})(n)});async function Ce(e,t){const o=t&&m(t);try{return await Pe(w(import.meta.resolve(e+"/package.json",o)))}catch{}try{const t=await(async e=>{const t=new Set(["",i]);for(let o=await n(e,{encoding:"utf-8"});!t.has(o);o=f(o)){t.add(o);const e=a(o,"package.json");try{return await r(e,s.R_OK),await Pe(e)}catch{}}throw new Error(`package.json not found in ${e} or any parent folder`)})(f(w(import.meta.resolve(e,o))));if(t.packageJson.name===e)return t}catch{}return null}function Re(e,t,n,o){for(const[r,s]of t)e.set(n+r.substring(1),s?o+qe(s.substring(1)):null)}function Te(e){return Object.fromEntries([...e.entries()].filter(([e,t])=>null!==t).map(([e,t])=>[qe(e),t]))}async function Ae(e,t,n,o){const r=new Map,s=[],i=[];for(const[t,a]of e){const e=t.indexOf("*"),c=a?.indexOf("*")??-1;if(!n(t)||null!==a&&(!o(a)||-1!==c!=(-1!==e)))throw new Error(`invalid entry: ${JSON.stringify(t)} => ${JSON.stringify(a)}`);if(-1!==e){const n=[t.length-1,t.substring(0,e),t.substring(e+1)],o=[e,t.length];a?s.push({i:n,u:[a.length-1,a.substring(0,c),a.substring(c+1)],l:o}):i.push({i:n,l:o})}else r.set(t,{u:a,l:[Number.POSITIVE_INFINITY,0]})}if(s.length){const e=await t();for(const t of e)for(const e of s){const n=Be(e.u,t);if(!1!==n){const o=e.i[1]+n+e.i[2],s=r.get(o);(!s||Ie(e.l,s.l)>0)&&r.set(o,{u:t,l:e.l})}}}for(const e of i)for(const[t,n]of r)null!==n.u&&Ie(e.l,n.l)>0&&!1!==Be(e.i,t)&&(n.u=null);return[...r].map(([e,t])=>[e,t.u])}const Be=(e,t)=>!!(t.length>=e[0]&&t.startsWith(e[1])&&t.endsWith(e[2]))&&t.substring(e[1].length,t.length-e[2].length),Ie=(e,t)=>e[0]-t[0]||e[1]-t[1],qe=e=>encodeURIComponent(e).replaceAll(/%2f/gi,"/").replaceAll(/%40/g,"@").replaceAll(/%23/g,"#");async function He(e,{environment:o=["browser","import","production"],mapFilePath:r="/importmap.json",sourcesBasePath:s="",modulesBasePath:i="/node_modules",...c}){const f=new y,u=await(async(e,t,n,o,r)=>{n.endsWith("/")&&(n=n.substring(0,n.length-1)),o.endsWith("/")&&(o=o.substring(0,o.length-1));const s=new Set,i=new Map,a=e.map(e=>{const t=r(e.packageJson,e.dir);let a=t;for(let e=2;s.has(a);++e)a=`${t}_${e}`;s.add(a);const c={m:e,h:[],$:e.isRoot?o:`${n}/${qe(a)}`,v:e.isRoot?null:a};return i.set(e.id,c),c});await Promise.all(a.map(async e=>{e.h=await Ae(((e,t)=>ze((e=>{const t=e.exports;if(void 0===t)return{".":{browser:Me(e.browser),module:Me(e.module),default:Me(e.main)??"./index.js"},"./*":"./*"};if(Y(t)||"object"!=typeof t||!t)return{".":t};for(const e of Object.keys(t))if(e.startsWith("."))return t;return{".":t}})(e),t))(e.m.packageJson,t),async()=>e.j??=await e.m.getFilePaths(),e=>"."===e||e.startsWith("./"),e=>e.startsWith("./"))}));const c=[];let f=new Map;const u=[];for(const e of a){const n=new Map;e.m.packageJson.name&&e.m.packageJson.exports&&Re(n,e.h,e.m.packageJson.name,e.$);for(const[t,o]of e.m.dependencies){const e=i.get(o);if(!e)throw new Error("internal reference mismatch");Re(n,e.h,t,e.$)}const o=await Ae(Ne(e.m.packageJson,t),async()=>(e.j??=await e.m.getFilePaths(),new Set([...n.keys(),...e.j])),e=>e.startsWith("#")&&e.length>1,e=>e.startsWith("./")||!e.startsWith("."));for(const[t,r]of o){if(!r){n.set(t,null);continue}const o=n.get(r);if(void 0!==o)n.set(t,o);else{if(!r.startsWith("./"))throw new Error(`unable to resolve import ${r}`);n.set(t,e.$+qe(r.substring(1)))}}e.m.isRoot?f=n:u.push([e.$+"/",n]),c.push({dir:e.m.dir,subPath:e.v,imports:n})}const p=[];for(const[e,t]of u){const n=new Map(t);for(const[e,t]of f)n.get(e)===t&&n.delete(e);n.size&&p.push([e,Te(n)])}return{packages:c,importMap:{imports:Te(f),scopes:Object.fromEntries(p)}}})(await(async e=>{const o=await t(e);if(o.isDirectory())e=a(e,"package.json");else if(!o.isFile())throw new Error(`invalid package.json path: ${e}`);const r=await Pe(await n(e));r.isRoot=!0;const s=new Set([r.id]),i=[r];for(const e of i){const t=new Map;for(const n of Object.keys(e.packageJson.peerDependencies??{}))t.set(n,!1);for(const[n,o]of Object.entries(e.packageJson.peerDependenciesMeta??{}))t.has(n)&&t.set(n,o.optional??!1);for(const n of Object.keys(e.packageJson.optionalDependencies??{}))t.set(n,!0);for(const n of Object.keys(e.packageJson.dependencies??{}))t.set(n,!1);const n=a(e.dir,"a.js");await Promise.all([...t].map(async([t,o])=>{const r=await Ce(t,n);if(!r){if(o)return;throw new Error(`package ${t} not found (required by ${e.dir})`)}s.has(r.id)||(s.add(r.id),i.push(r)),e.dependencies.set(t,r.id)}))}return i})(e),new Set(o),i,s,Ue);for(const{dir:e,subPath:t}of u.packages)t&&f.mount("/"+t,await b(e,{...c,hide:[...c.hide??[],"node_modules"],indexFiles:[]}));const p=JSON.stringify(u.importMap);if(r){const e={headers:c.headers,encodings:["br","gzip"],minCompression:100};f.get(r,$(Buffer.from(p,"utf-8"),"application/importmap+json",e)),f.get(r+".js",$(Buffer.from(`const s=document.createElement('script');s.type='importmap';s.textContent=JSON.stringify(${p});document.head.append(s);`,"utf-8"),"text/javascript; charset=utf-8",e))}return Object.assign(f,{importMapJSON:p})}const Ue=e=>(e.name||"-")+(e.version?`@${e.version}`:""),De=new Map([[".mjs","module"],[".cjs","commonjs"],[".mts","module-typescript"],[".cts","commonjs-typescript"],[".json","json"],[".wasm","wasm"]]);Z({resolve(e,t,n){if("web-listener"===e)e=a(f(w(import.meta.url)),"index.js");else{const n=URL.parse(e,t.parentURL);if(n&&"file:"===n.protocol&&re(w(n)))return{url:n.toString(),format:De.get(u(n.pathname)),shortCircuit:!0}}return n(e,t)},load(e,t,n){if(e.startsWith("file://")){const t=w(e),n=re(t);if(n&&!n.isDirectory)return{source:(e=>{const t=e.meta();if(!t.s)return Buffer.alloc(0);const n=Buffer.alloc(t.s),o=K(e.zipFilePath,W.O_RDONLY);try{if(Q(o,n,0,t.s,t.p)!==t.s)throw new Error("zip content has changed");return t.z?X(n,{maxOutputLength:e.stat().size}):n}finally{V(o)}})(n),format:De.get(u(t))??"commonjs",shortCircuit:!0}}return n(e,t)}});const Ge=new Set,Le=new Set;async function Fe(e,t,n){Le.has(e)||(Ge.has(e)&&n(`${e} has already been loaded into the module cache and will not be updated`),Le.add(e)),await ne(e,!0);const o=await import(e),r=o?.[t||"default"];if(!r||"function"!=typeof r&&"object"!=typeof r)throw new Error(`${e} must export a request handler ${t?`named ${JSON.stringify(t)}`:"as default"}`);return r}async function Ze(e,n){const o=await t(e).catch(()=>null);if(o?.isDirectory())return"static-paths"===n.mode?v(e,n):j(e,n);const r=await ne(e,!1);if(!r)throw new _e(`content to serve not found at ${e}`);const s=r.root.find(r.remaining);if(!s?.isDirectory)throw new Error(`/${r.remaining.join("/")} in ${r.path} is not a directory`);const i=n;return i.negotiator||(i.negotiator=new x([k(["gzip"])])),S(s,i)}class Ke{constructor(e){this.k=e?e=>e:e=>e.toLowerCase(),this.S=new Map,this.O=[],this._=""}add(e,t){if("/"===e[0])this.S.set(this.k(e),t);else{if("~"!==e[0])throw new Error(`invalid URL: ${e}`);{const n="*"!==e[1],o=e.substring(n?1:2);this.O.push({N:new RegExp(o,n?"":"i"),u:t})}}}setDefault(e){this._=e}get(e){const t=this.S.get(this.k(e));if(void 0!==t)return t;for(const t of this.O){t.N.lastIndex=0;const n=t.N.exec(e);if(n)return t.u.replaceAll(/\$(?:([0-9]+)|([a-zA-Z][a-zA-Z0-9]*))/g,(e,t,o)=>t?n[Number.parseInt(t)]??"":n.groups&&Object.prototype.hasOwnProperty.call(n.groups,o)?n.groups[o]??"":"")}return this._}}function*We(e){let t=[];const n=/(\s+|#[^\n]*)|(;)|(?:"((?:[^"\\]+|\\.)*)")|(?:'((?:[^'\\]+|\\.)*)')|((?:[^#;\s\\"']+|\\.)+)/y;for(;n.lastIndex<e.length;){const o=n.exec(e);if(!o)throw new Error("invalid nginx syntax");const[,r,s,i,a,c]=o;if(!r)if(s)yield t,t=[];else if(c&&!c.includes("\\"))t.push({token:c,literal:!0});else{const e=i??a??c;if(void 0===e)throw new Error("nginx tokenisation error");t.push({token:e.replaceAll(/\\(.)/g,"$1"),literal:!1})}}if(t.length)throw new Error("unterminated statement - ensure all statements end with a semicolon")}const Qe=(e,t,n="raw")=>e.replaceAll(/\$\{(?:(raw|html|json|int|uri)\()?([^${}():]+)(?:(\))?:-((?:[^})\\]|\\.)*))?(\))?\}/g,(e,o,r,s,i,a)=>{if((s?1:0)+(a?1:0)!=(o?1:0))return e;const c=t(r),f=Ve[o??n];let u=c.M;return s&&u&&f&&(u=f(u)),u??=i?.replaceAll(/\\(.)/g,"$1")??"",a&&f&&(u=f(u)),!o&&f&&c.J!==n&&(u=f(u)),Xe(u)}),Ve={html:e=>Xe(e).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;"),json:e=>JSON.stringify(Xe(e)),int:e=>{if(Y(e))return"0";const t=/^(?:\+|(-))?0*(\d+)$/.exec(e);return t?(t[1]??"")+t[2]:"0"},uri:e=>Y(e)?e.map(encodeURIComponent):encodeURIComponent(e)},Xe=e=>Y(e)?e.join("/"):e,Ye=new E(0),et=e=>(Ye.set(e,e.url?.length??0),M);async function tt(e,t=()=>{},n){const o=new y;n&&o.use(O((e,t)=>{const o=Date.now();return _(e,()=>{const r=Date.now()-o;let s=N(e);const i=Ye.get(e);if(i>1){const e=s.length+1-i;s=s.substring(0,e)+("?"===s[e]?"?":"")+"[***]"}n({method:e.method??"GET",path:s,status:t.statusCode,duration:r})}),M}));for(const n of e)switch(n.type){case"nested":o.mount(n.path,n.maskSubpaths?et:null,await tt(n.mount,t));break;case"headers":{const e=new Map(Object.entries(n.headers));o.mount(n.path,(t,n)=>(n.setHeaders(e),M));break}case"files":if("/dev/null"!==n.dir){const e=n.options.negotiation&&n.options.negotiation.length>0?new x(n.options.negotiation):void 0;o.mount(n.path,J(await Ze(n.dir,{...n.options,negotiator:e}),n.options))}break;case"proxy":o.mount(n.path,z(n.target,{...n.options,responseHeaders:[(e,t,o)=>({...o,...n.options.headers})]}));break;case"fixture":{const e=(e,t)=>{for(const[o,r]of Object.entries(n.headers))"string"==typeof r?t.setHeader(o,Qe(r,nt(e))):"number"==typeof r?t.setHeader(o,r):t.setHeader(o,r.map(t=>Qe(t,nt(e))));t.statusCode=n.status,t.end(Qe(n.body,nt(e)))};"GET"===n.method&&o.onRequest("HEAD",n.path,e),o.onRequest(n.method,n.path,e);break}case"redirect":o.at(n.path,O((e,t)=>{let o=Qe(n.target,nt(e),"uri");"/"===n.target[0]&&(o=o.replace(/^\/{2,}/,"/")),t.setHeader("location",o),t.statusCode=n.status,t.end()}));break;case"redirect-map":{const e=new Ke(n.options.caseSensitive);if("string"==typeof n.mapping){let t;try{t=await oe(n.mapping)}catch(e){throw e&&"object"==typeof e&&"code"in e&&"ENOENT"===e.code?new _e(`redirect-map file not found at ${n.mapping}`):e}e:for(const n of We(t)){const t=n[0];if(t.literal)switch(t.token){case"default":if(2===n.length){e.setDefault(n[1].token);continue e}break;case"hostnames":throw new Error("redirect-map does not support hostnames");case"include":throw new Error("redirect-map does not support nested mapping files")}if(2!==n.length)throw new Error(`unknown statement in mapping file: ${n.map(e=>JSON.stringify(e)).join(" ")}`);e.add(t.token,n[1].token)}}else for(const[t,o]of Object.entries(n.mapping))e.add(t,o);o.use((t,o)=>{const r=e.get(t.url??"/");return r&&r!==(t.url??"/")?(o.setHeader("location",r),o.statusCode=n.status,o.end()):M});break}case"dependencies":o.mount(n.path,await He(n.package,{...n.options,modulesBasePath:n.path}));break;case"custom":{const e=await Fe(n.import,n.namedExport,t),r=n.maskSubpaths?et:null;"string"==typeof n.method&&"get"===n.method.toLowerCase()?o.get(n.path,r,e):n.method?o.onRequest(n.method,n.path,r,e):o.mount(n.path,r,e);break}}return o}const nt=e=>t=>"?"===t?{M:P(e)||void 0,J:"uri"}:"?"===t[0]?{M:C(e,t.substring(1)),J:"raw"}:{M:R(e,t),J:"raw"};class ot{constructor(){this.P=!1,this.C=!1,this.R=!1,this.T=new Map,this.A=new Set}async set(e,t,n,o){if(!this.C&&!this.R){clearTimeout(this.B),this.B=void 0;try{this.C=!0;const r=[],s=[];let i=!1;const a=this.A;this.A=new Set;e:for(let e=0;e<t.length;++e){const o=t[e];for(const e of a)if(it(e.config,o)){i||=e.isRunning(),this.A.add(e),a.delete(e);continue e}i=!0,s.push(async()=>{this.A.add(await this.I(o,n))})}for(const e of a)r.push(e.close);const c=new Set;for(let t=0;t<e.length;++t){const o=e[t],r=o.port;r<=0||r>65535?n(0,{type:"warn",message:`servers[${t}] must have a specific port from 1 to 65535`}):c.has(r)?n(0,{type:"warn",message:`skipping servers[${t}] because port ${r} has already been defined`}):(c.add(r),s.push(async()=>{const e=await this.q(o,this.T.get(r),n);e?this.T.set(r,e):this.T.delete(r)}))}this.P||=s.length>0;for(const[e,t]of this.T)c.has(e)||(s.push(t.close),this.T.delete(e));let f=!1;const u=async e=>{let t=[];if(await Promise.all(e.map(async e=>{try{await e()}catch(e){i&&!this.R&&e instanceof _e?(n(1,{type:"warn",message:`${e.message} (retrying)`}),f=!0):t.push(e)}})),t.length)throw await this.H(n),t.length>1?new AggregateError(t):t[0]};await u(r),await u(s),(()=>{for(const e of Le)Ge.add(e);Le.clear()})(),this.R?this.H(n):f?this.B=setTimeout(()=>{se(),this.set(e,t,n,o)},1e3):this.T.size?n(1,{message:"all servers ready"}):n(1,{message:"no servers configured"})}catch(e){o(e)}finally{this.C=!1}}}async I(e,t){const n={service:e.command,serviceCol:"35"},o=new AbortController;return new Promise((r,s)=>{t(2,{...n,type:"detail",message:"starting"});let i=!0;const a=l(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?rt(a.stdout,e=>t(0,{...n,thread:"stdout",message:e})):a.stdout.resume(),e.options.displayStderr?rt(a.stderr,e=>t(0,{...n,thread:"stderr",message:e})):a.stderr.resume(),a.addListener("error",e=>{t(0,{...n,type:"error",message:`startup failed: ${e.message}`}),i=!1,s(e)}),a.addListener("exit",(e,o)=>{t(2,{...n,message:"closed",stats:null!==e?{code:e}:{signal:o}}),i=!1}),a.addListener("spawn",()=>r({config:e,isRunning:()=>i,close:()=>new Promise(o=>{null!==a.signalCode||null!==a.exitCode?o():(t(2,{...n,type:"detail",message:"closing",stats:{signal:e.options.killSignal}}),a.addListener("exit",o),a.kill(e.options.killSignal))})}))})}async q({port:e,host:t,options:n,mount:o},r,s){const i={service:`http://${t}:${e}`,serviceCol:"34"},a=await tt(o,e=>s(1,{...i,type:"warn",message:e}),n.logRequests?e=>s(0,{...i,method:e.method,path:e.path,status:e.status,stats:{duration:e.duration}}):void 0),c=new T(a);let f,u;if(c.addEventListener("error",e=>{e.preventDefault();const{error:t,context:n,request:o}=e.detail;(A(t,B)?.statusCode??500)>=500&&s(0,{...i,thread:n,type:"error",path:o?.url,message:String(t)})}),r&&t===r.host&&((e,t)=>{for(const n of at)if(e[n]!==t[n])return!1;return!0})(n,r.options))f=r.server,s(2,{...i,message:"updated"}),r.detach();else{if(r?(s(2,{...i,type:"detail",message:"restarting (step 1: shutdown)"}),await r.close(),s(2,{...i,type:"detail",message:"restarting (step 2: start)"})):s(2,{...i,type:"detail",message:"starting"}),this.R)return;f=F(n),f.setTimeout(n.socketTimeout),u=st(f,e,t,n.backlog)}const p=c.attach(f,n);return u&&(await u,await new Promise(e=>setTimeout(e,1)),s(2,{...i,message:"ready"})),{host:t,options:n,server:f,detach:()=>p("restart",n.restartTimeout),close:()=>new Promise(e=>{const t=p("shutdown",n.shutdownTimeout,!0,()=>{f.close(()=>{s(2,{...i,message:"closed"}),e()}),f.closeAllConnections()}).countConnections();t>0&&s(2,{...i,type:"detail",message:"closing",stats:{connections:t}})})}}async H(e){this.T.size&&(e(2,{type:"detail",message:"shutting down"}),await Promise.all([...this.T.values(),...this.A].map(e=>e.close()))),this.P&&e(2,{message:"shutdown complete"})}shutdown(e){this.R||(clearTimeout(this.B),this.B=void 0,this.R=!0,this.C||this.H(e))}}function rt(e,t){let n="";const o=e=>t(e.replaceAll(/\x1b\[[\d;]*[A-K]/g,"").replaceAll(/[\x00-\x1f]/g,e=>`\\x${e.charCodeAt(0).toString(16).padStart(2,"0")}`));e.addListener("data",e=>{n+=e;const t=n.split("\n");n=t.pop();for(const e of t)o(e)}),e.addListener("end",()=>{n&&o(n)})}const st=async(e,t,n,o=511)=>new Promise((r,s)=>{e.once("error",s),e.listen(t,n,o,()=>{e.off("error",s),r()})});function it(e,t){return e.command===t.command&&e.arguments.length===t.arguments.length&&e.arguments.every((e,n)=>e===t.arguments[n])&&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 at=["requestTimeout","keepAliveTimeout","keepAliveTimeoutBuffer","connectionsCheckingInterval","headersTimeout","highWaterMark","maxHeaderSize","noDelay","requireHostHeader","keepAlive","keepAliveInitialDelay","uniqueHeaders","backlog","socketTimeout"];function ct(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 ft=(e,t,n=t+"s")=>1===e?`1 ${t}`:`${e} ${n}`,ut=e=>e<2e3?`${e}B`.padStart(8," "):`${(e/1024).toFixed(1)}kB`.padStart(8," "),pt=["none","ready","progress"],lt=(e,t)=>{const n=e.isTTY&&!process.env.NO_COLOR?(e,t)=>e?`\x1b[${e}m${t}\x1b[0m`:t:(e,t)=>t,o=pt.indexOf(t);return(t,r)=>{if(t>o)return;const s=[];if(r.service&&(r.serviceCol?s.push(n(r.serviceCol,r.service)):s.push(r.service)),r.thread&&s.push(n("2",`[${r.thread}]`)),"warn"===r.type?s.push(n("33","warning")+":"):"error"===r.type&&s.push(n("91","error")+":"),void 0!==r.method&&s.push(n("1",r.method.replaceAll(/[^a-zA-Z0-9\-_]/g,"?")||"?")),void 0!==r.path&&s.push(r.path),void 0!==r.status&&s.push(n(dt[r.status/100|0]??"",String(r.status))),r.message){const e=r.message instanceof Error?r.message.message:String(r.message);"detail"===r.type?s.push(n("2",e)):s.push(e)}r.stats&&s.push(n("2",`(${Object.entries(r.stats).map(([e,t])=>"duration"===e?`${t}ms`:`${e}=${t}`).join("; ")})`)),e.write(`${s.join(" ")}\n`)}},dt=["","37","32","36","31","41;97"];process.on("SIGUSR1",()=>{});const wt=process.stderr;let mt=lt(wt,"progress");function ht(e){process.stdin.destroy(),mt(0,{type:"error",message:e})}process.on("unhandledRejection",ht),process.on("uncaughtException",ht);const gt=(e=>{const t=[];for(let n=0;n<e.length;++n){const o=e[n];if("--"===o)continue;const r=/^--([^ =\-][^ =]*)=(.*)$/.exec(o);if(r){t.push([r[1],r[2]]);continue}const s=/^-([^ =]*)([^ =])=(.*)$/.exec(o);if(s&&"-"!==o[1]){for(const e of s[1])t.push(["-"+e,""]);t.push(["-"+s[2],s[3]]);continue}if("-"!==o[0]||"-"===o){if(0!==n)throw new Error(`value without key: ${o}`);t.push(["",o]);continue}let i=e[n+1];if(i&&"-"===i[0]&&i.length>1&&(i=void 0),void 0!==i&&++n,"-"===o[1])t.push([o.slice(2),i??""]);else{for(const e of o.slice(1,o.length-1))t.push(["-"+e,""]);t.push(["-"+o[o.length-1],i??""])}}const n=new Map;for(const[e,o]of t){const t=(ie.get(e)??e).toLowerCase(),r=ae.get(t);if(!r)throw new Error(`unknown flag: ${e}`);let s;switch(r.type){case"string":s=o;break;case"number":s=Number.parseFloat(o);break;case"boolean":s=["","on","true","yes","y","1"].includes(o.toLowerCase())}if(r.multi){let e=n.get(t);e||(e=[],n.set(t,e)),e.push(s)}else{if(n.has(t))throw new Error(`multiple values for ${t}`);n.set(t,s)}}return n})(process.argv.slice(2)),yt=f(w(import.meta.url));if(gt.get("version")||gt.get("help")){let t={name:"web-listener",version:"unknown"};try{t=JSON.parse(await e(a(yt,"package.json"),"utf-8"))}catch{}gt.get("help")?d("man",["-M",yt,t.name],{stdio:["inherit","inherit","inherit"]}):process.stdout.write(`${t.name} ${t.version}\n`),process.exit(0)}(async()=>{const t=new ot;process.on("unhandledRejection",()=>t.shutdown(mt)),process.on("uncaughtException",()=>t.shutdown(mt));const n=function(e){const t=n=>{const o=n.$ref;if(o&&o.startsWith("#/$defs/")){const t=Oe(e.$defs,o.substring(8));if(!t)throw new Error(`unable to find ${o} in schema`);n={...t,...n}}const r=((e,t)=>{if(void 0!==e.const)return we(e.const);if(e.enum)return me(new Set(e.enum));if(e.anyOf)return he(e.anyOf.map(t));switch(e.type){case"null":return ge;case"array":return ye(t(e.items));case"boolean":return be;case"integer":return $e(!0,e.minimum,e.maximum);case"number":return $e(!1,e.minimum,e.maximum);case"object":const n=e.additionalProperties??!0,o=xe(new Map(Object.entries(e.properties??{}).map(([e,n])=>[e,t(n)])),"object"==typeof n?t(n):n?ke:Se,e.required??[]);if(e.$comment?.startsWith("flatten:")){const t=e.$comment.substring(8);return(e,n)=>o(e,n)[t]}return o;case"string":if("regex"===e.format)return ve();let r=null;return e.pattern&&(r=new RegExp(e.pattern)),je(r,e.format??"");default:throw new Error(`unknown part type ${JSON.stringify(e)}`)}})(n,t);if(void 0!==n.default){const e=JSON.stringify(n.default);return(t,n)=>r(void 0===t?JSON.parse(e):t,n)}return(e,t)=>void 0===e?void 0:r(e,t)};return t(e)}(await(async()=>JSON.parse(await e(a(f(w(import.meta.url)),"schema.json"),"utf-8")))());function o(){process.stdin.destroy(),t.shutdown(mt)}async function r(){se();const e=await(async(e,t)=>{const n=(e,n=[])=>t.get(e)??n,o=e=>t.get(e),r=e=>t.get(e),s=o("config-file"),i=o("config-json"),a=r("port"),c=o("host"),f=n("dir"),u=n("exec").map(e=>e.split(" ")),p=n("ext").map(e=>e.startsWith(".")?e:`.${e}`),l=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=o("404"),w=o("spa"),m=o("proxy"),h=o("dependencies"),g=r("min-compress"),y=n("mime"),b=n("mime-types"),$=n("redirect-map"),v=o("log"),j=o("log-format");if(Number(Boolean(s))+Number(Boolean(i))+Number(Boolean(m))>1)throw new Error("multiple config files are not supported");let x;s?x=await fe(null,s,e):i?x=await fe(i,"",e):(f.length||f.push("."),x=e({servers:[{port:8080,mount:[]}]},{file:"",path:""}));for(const e of u)x.backgroundTasks.push({command:e[0],arguments:e.slice(1),cwd:process.cwd(),environment:{},options:{killSignal:"SIGTERM",displayStdout:!0,displayStderr:!0}});const k=1===x.servers.length?x.servers[0]:void 0;if(void 0!==a){if((0|a)!==a)throw new Error("port must be an integer");if(!k)throw new Error("cannot specify port on commandline when defining multiple servers");k.port=a}if(void 0!==c)for(const e of x.servers)e.host=c;if(f.length||w||d){if(!k)throw new Error("cannot specify dir, spa, or 404 on commandline when defining multiple servers");let e;w?e={statusCode:200,filePath:w}:d&&(e={statusCode:404,filePath:d});for(let t=0;t<f.length;++t){const n=f[t],o=t===f.length-1;k.mount.push({type:"files",path:"/",dir:n,options:{fallback:o?e:void 0}})}}if(m){if(!k)throw new Error("cannot specify proxy on commandline when defining multiple servers");k.mount.push({type:"proxy",path:"/",target:m,options:{}})}if($.length>0)for(const e of x.servers)for(const t of $)e.mount.unshift({type:"redirect-map",mapping:t,status:307,options:{caseSensitive:!1}});if(void 0!==h)for(const e of x.servers)e.mount.push({type:"dependencies",path:"/node_modules",package:h,options:{}});const S=(e,t)=>ce(x.servers,n=>{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,n]of le)t.get(e)&&S("encoding",n);if(p.length&&ce(x.servers,e=>{"files"===e.type&&(e.options.implicitSuffixes=p)}),t.get("no-cache")&&l.push(["cache-control","no-store, no-cache, max-age=0"]),l.length){const e={type:"headers",path:"/",headers:{}};ce(x.servers,e=>{switch(e.type){case"headers":case"fixture":e.headers=pe(e.headers,l);break;case"files":case"proxy":case"dependencies":e.options.headers=pe(e.options.headers,l)}});for(const t of x.servers){let n=t.mount[0];"headers"===n?.type&&"/"===n.path||(n={...e},t.mount.unshift(n)),n.headers=ue(n.headers,l)}}switch((y.length||b.length)&&(x.mime=[...de(x.mime),...y,...b.map(e=>`file://${e}`)]),t.get("write-compressed")&&(x.writeCompressed=!0),void 0!==g&&(x.minCompress=g),t.get("no-serve")&&(x.noServe=!0),v){case void 0:break;case"none":case"ready":case"progress":x.log=v;for(const e of x.servers)e.options.logRequests=!1;for(const e of x.backgroundTasks)e.options.displayStderr=!1,e.options.displayStdout=!1;break;case"full":x.log="progress";break;default:throw new Error(`unknown log level: ${v}`)}switch(j){case void 0:break;case"text":case"json":x.logFormat=j;break;default:throw new Error(`unknown log format: ${j}`)}return x})(n,gt);mt="json"===e.logFormat?((e,t)=>{const n=pt.indexOf(t);return(t,{serviceCol:o,message:r,...s})=>{t>n||(r instanceof Error?r=r.message:void 0!==r&&(r=String(r)),e.write(`${JSON.stringify({...s,message:r})}\n`))}})(wt,e.log):lt(wt,e.log),await(async e=>{const t=[];for(const n of Y(e)?e:[e])"string"!=typeof n?t.push(new Map(Object.entries(n))):n.startsWith("file://")?t.push(H(await oe(n.substring(7)))):t.push(U(n));D();for(const e of t)G(e)})(e.mime),e.writeCompressed&&await(async(e,t,n)=>{let o=0;for(const r of e)for(const e of r.mount)if("files"===e.type){const r=e.options.negotiation?.find(e=>"encoding"===e.feature);if(!r?.options?.length){n(2,{message:`skipping ${e.dir} because no compression is configured`});continue}const s=r.match?` matching ${r.match}`:"";n(2,{message:`compressing files in ${e.dir}${s} using ${r.options.map(e=>e.value).join(", ")}`});const i=I(r.match,!0),a=await q(e.dir,r.options,{...e.options,minCompression:t,filter:(e,t)=>!["image","video","audio","font"].includes(t.split("/")[0])&&i(p(e))}),c=ct(a.filter(({mime:e})=>e.startsWith("text/"))),f=ct(a.filter(({mime:e})=>!e.startsWith("text/")));n(2,{message:`text: ${ut(c.rawSize)} / ${ut(c.bestSize)} compressed`}),n(2,{message:`other: ${ut(f.rawSize)} / ${ut(f.bestSize)} compressed`}),o+=c.created+f.created}n(2,{message:`${ft(o,"compressed file")} written`})})(e.servers,e.minCompress,mt),e.noServe?o():t.set(e.servers,e.backgroundTasks,mt,e=>{if(e instanceof AggregateError)for(const t of e.errors)mt(0,{type:"error",message:t});else mt(0,{type:"error",message:e});process.stdin.destroy(),process.exit(1)})}function s(){return mt(2,{message:"refreshing config"}),r()}r(),process.on("SIGHUP",()=>s()),process.stdin.on("data",e=>{e.includes("\n")&&s()});let i=!1;process.on("SIGINT",()=>{i||(i=!0,wt.isTTY&&wt.write("\n"),o())})})();
package/schema.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json-schema.org/draft-07/schema#","title":"Configuration for running one or more servers via the web-listener CLI","type":"object","additionalProperties":false,"required":["servers"],"properties":{"$schema":{"type":"string"},"servers":{"type":"array","items":{"anyOf":[{"$ref":"#/$defs/server"},{"$ref":"#/$defs/server-ref"}]}},"backgroundTasks":{"type":"array","items":{"$ref":"#/$defs/task"},"default":[]},"mime":{"anyOf":[{"type":"array","items":{"$ref":"#/$defs/mime"}},{"$ref":"#/$defs/mime"}],"default":[]},"writeCompressed":{"title":"Generate and save zstd, brotli, gzip, or deflate files for each served file before starting","type":"boolean","default":false},"minCompress":{"title":"The minimum number of bytes that compression must save, otherwise the file is not written","type":"integer","default":300},"noServe":{"title":"Stop after validating the configuration and optionally writing any compressed files","type":"boolean","default":false},"log":{"title":"Set the logging level. \"ready\" prints only the \"all servers ready\" line, \"progress\" prints server startup and shutdown progress.","enum":["none","ready","progress"],"default":"progress"},"logFormat":{"title":"Set the logging format. \"text\" is the most human-readable, \"json\" prints each log message as a machine-readable JSON document (one per line).","enum":["text","json"],"default":"text"}},"$defs":{"server":{"title":"Configuration for one server","type":"object","additionalProperties":false,"defaultSnippets":[{"label":"server","description":"Defines a server on a given port","body":{"port":"^${1:80}","mount":[]}}],"required":["port","mount"],"properties":{"port":{"type":"integer","minimum":1,"maximum":65535},"host":{"type":"string","default":"localhost"},"options":{"type":"object","default":{},"additionalProperties":false,"properties":{"requestTimeout":{"type":"integer"},"keepAliveTimeout":{"type":"integer"},"keepAliveTimeoutBuffer":{"type":"integer"},"connectionsCheckingInterval":{"type":"integer"},"headersTimeout":{"type":"integer"},"highWaterMark":{"type":"integer"},"maxHeaderSize":{"type":"integer"},"noDelay":{"type":"boolean"},"requireHostHeader":{"type":"boolean"},"keepAlive":{"type":"boolean"},"keepAliveInitialDelay":{"type":"integer"},"uniqueHeaders":{"type":"array","items":{"type":"string"}},"backlog":{"type":"integer","default":511},"socketTimeout":{"type":"integer"},"rejectNonStandardExpect":{"type":"boolean","default":false},"autoContinue":{"type":"boolean","default":false},"logRequests":{"type":"boolean","default":true},"restartTimeout":{"type":"integer","default":2000},"shutdownTimeout":{"type":"integer","default":500}}},"mount":{"type":"array","items":{"title":"Service to mount on the server","defaultSnippets":[{"label":"files","body":{"type":"files","path":"${1:/}","dir":"${2:.}"}},{"label":"proxy","body":{"type":"proxy","path":"${1:/}","target":"${2:http://localhost:8080}"}},{"label":"fixture","body":{"type":"fixture","method":"${1:GET}","path":"${2:/}","status":"^${3:200}","body":"$4"}},{"label":"redirect","body":{"type":"redirect","path":"$1","target":"$2","status":"^${3:307}"}},{"label":"redirect-map","body":{"type":"redirect-map","mapping":"^${1:{}}","status":"^${2:307}","options":{"caseSensitive":"^${3:false}"}}},{"label":"dependencies","body":{"type":"dependencies","path":"${1:/node_modules}","package":"${2:./package.json}"}},{"label":"delegate","body":{"type":"delegate","path":"${1:/}","config":{"file":"$2"}}},{"label":"custom","body":{"type":"custom","path":"${1:/}","method":"^${2:null}","import":"$3","namedExport":"^${4:null}"}}],"anyOf":[{"$ref":"#/$defs/mount-delegate"},{"$ref":"#/$defs/mount-headers"},{"$ref":"#/$defs/mount-files"},{"$ref":"#/$defs/mount-proxy"},{"$ref":"#/$defs/mount-fixture"},{"$ref":"#/$defs/mount-redirect"},{"$ref":"#/$defs/mount-redirect-map"},{"$ref":"#/$defs/mount-dependencies"},{"$ref":"#/$defs/mount-custom"}]}}}},"mount-delegate":{"type":"object","additionalProperties":false,"required":["type","config"],"properties":{"type":{"const":"delegate"},"path":{"type":"string","default":"/"},"config":{"$ref":"#/$defs/server-ref"},"maskSubpath":{"type":"boolean","default":false}}},"mount-headers":{"type":"object","additionalProperties":false,"required":["type","headers"],"properties":{"type":{"const":"headers"},"path":{"type":"string","default":"/"},"headers":{"$ref":"#/$defs/headers"}}},"mount-files":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"const":"files"},"path":{"type":"string","default":"/"},"dir":{"type":"string","default":".","format":"uri-reference"},"options":{"type":"object","default":{},"additionalProperties":false,"properties":{"mode":{"enum":["dynamic","static-paths"],"default":"dynamic"},"fallback":{"type":"object","additionalProperties":false,"required":["filePath"],"properties":{"statusCode":{"$ref":"#/$defs/status","default":200},"filePath":{"type":"string"}}},"headers":{"$ref":"#/$defs/headers"},"dynamicHeaders":{"type":"array","items":{"enum":["etag","last-modified"]},"default":["etag","last-modified"]},"verbose":{"type":"boolean","default":false},"subDirectories":{"anyOf":[{"type":"boolean"},{"type":"integer","minimum":0}],"default":true},"caseSensitive":{"enum":["exact","filesystem","force-lowercase"],"default":"exact"},"allowAllDotfiles":{"type":"boolean","default":false},"allowAllTildefiles":{"type":"boolean","default":false},"allowDirectIndexAccess":{"type":"boolean","default":false},"hide":{"type":"array","items":{"$ref":"#/$defs/stringOrRegex"},"default":[]},"allow":{"type":"array","items":{"type":"string"},"default":[".well-known"]},"indexFiles":{"type":"array","items":{"type":"string"},"default":["index.htm","index.html"]},"implicitSuffixes":{"type":"array","items":{"type":"string"},"default":[]},"negotiation":{"type":"array","default":[],"items":{"type":"object","additionalProperties":false,"required":["feature","options"],"properties":{"feature":{"enum":["type","language","encoding"]},"match":{"$ref":"#/$defs/stringOrRegex"},"options":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["value","file"],"properties":{"value":{"type":"string"},"for":{"type":"string","format":"regex"},"file":{"type":"string"}}}}}}}}}}},"mount-proxy":{"type":"object","additionalProperties":false,"required":["type","target"],"properties":{"type":{"const":"proxy"},"path":{"type":"string","default":"/"},"target":{"type":"string"},"options":{"type":"object","default":{},"additionalProperties":false,"properties":{"noDelay":{"type":"boolean"},"keepAlive":{"type":"boolean"},"keepAliveInitialDelay":{"type":"integer"},"keepAliveMsecs":{"type":"integer"},"agentKeepAliveTimeoutBuffer":{"type":"integer"},"maxSockets":{"type":"integer"},"maxFreeSockets":{"type":"integer"},"timeout":{"type":"integer"},"headers":{"$ref":"#/$defs/headers"},"blockRequestHeaders":{"type":"array","items":{"type":"string"}},"blockResponseHeaders":{"type":"array","items":{"type":"string"}},"servername":{"type":"string"},"ca":{"type":"string"},"cert":{"type":"string"},"sigalgs":{"type":"string"},"ciphers":{"type":"string"},"crl":{"type":"string"},"ecdhCurve":{"type":"string"},"key":{"type":"string"},"passphrase":{"type":"string"},"pfx":{"type":"string"},"sessionTimeout":{"type":"integer"},"maxCachedSessions":{"type":"integer"}}}}},"mount-fixture":{"type":"object","additionalProperties":false,"required":["type","path","body"],"properties":{"type":{"const":"fixture"},"path":{"type":"string"},"method":{"type":"string","default":"GET"},"status":{"$ref":"#/$defs/status","default":200},"headers":{"$ref":"#/$defs/headers"},"body":{"type":"string"}}},"mount-redirect":{"type":"object","additionalProperties":false,"required":["type","path","target"],"properties":{"type":{"const":"redirect"},"path":{"type":"string"},"status":{"$ref":"#/$defs/status","default":307},"target":{"type":"string"}}},"mount-redirect-map":{"type":"object","additionalProperties":false,"required":["type","mapping"],"properties":{"type":{"const":"redirect-map"},"mapping":{"anyOf":[{"type":"object","additionalProperties":{"type":"string"}},{"type":"string","format":"uri-reference"}]},"status":{"$ref":"#/$defs/status","default":307},"options":{"type":"object","default":{},"additionalProperties":false,"properties":{"caseSensitive":{"type":"boolean","default":false}}}}},"mount-dependencies":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"const":"dependencies"},"path":{"type":"string","default":"/node_modules"},"package":{"type":"string","default":"./package.json","format":"uri-reference"},"options":{"type":"object","default":{},"additionalProperties":false,"properties":{"environment":{"type":"array","items":{"type":"string"},"default":["browser","import","production"]},"mapFilePath":{"type":"string","default":"/importmap.json"},"sourcesBasePath":{"type":"string","default":"/"},"modulesBasePath":{"type":"string","default":"/node_modules"},"mode":{"enum":["dynamic","static-paths"],"default":"dynamic"},"headers":{"$ref":"#/$defs/headers"},"dynamicHeaders":{"type":"array","items":{"enum":["etag","last-modified"]},"default":["etag","last-modified"]},"verbose":{"type":"boolean","default":false},"subDirectories":{"anyOf":[{"type":"boolean"},{"type":"integer"}],"default":true},"caseSensitive":{"enum":["exact","filesystem","force-lowercase"],"default":"exact"},"allowAllDotfiles":{"type":"boolean","default":false},"allowAllTildefiles":{"type":"boolean","default":false},"hide":{"type":"array","default":[],"items":{"$ref":"#/$defs/stringOrRegex"}},"allow":{"type":"array","items":{"type":"string"},"default":[".well-known"]}}}}},"mount-custom":{"type":"object","additionalProperties":false,"required":["type","import"],"properties":{"type":{"const":"custom"},"path":{"type":"string","default":"/"},"method":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}},{"type":"null"}],"default":null},"import":{"type":"string","format":"uri-reference"},"namedExport":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null},"maskSubpath":{"type":"boolean","default":false}}},"headers":{"type":"object","default":{},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"array","items":{"type":"string"}}]}},"server-ref":{"type":"object","additionalProperties":false,"required":["file"],"properties":{"file":{"type":"string","format":"uri-reference"},"serverIndex":{"type":"integer","minimum":0},"serverPort":{"type":"integer","minimum":1,"maximum":65535},"includeMime":{"type":"boolean","default":true},"includeBackgroundTasks":{"type":"boolean","default":false}}},"task":{"title":"Configuration for a background task","type":"object","additionalProperties":false,"defaultSnippets":[{"label":"task","description":"Run an executable","body":{"command":"$1"}}],"required":["command"],"properties":{"command":{"anyOf":[{"type":"string","pattern":"[\\\\/]","format":"uri-reference"},{"type":"string","pattern":"^[^\\\\/]*$"}]},"arguments":{"type":"array","items":{"type":"string"},"default":[]},"cwd":{"type":"string","default":".","format":"uri-reference"},"environment":{"type":"object","additionalProperties":{"type":"string"},"default":{}},"options":{"type":"object","additionalProperties":false,"properties":{"uid":{"type":"integer","minimum":0},"gid":{"type":"integer","minimum":0},"killSignal":{"anyOf":[{"enum":["SIGABRT","SIGALRM","SIGBUS","SIGCHLD","SIGCONT","SIGFPE","SIGHUP","SIGILL","SIGINT","SIGIO","SIGIOT","SIGKILL","SIGPIPE","SIGPOLL","SIGPROF","SIGPWR","SIGQUIT","SIGSEGV","SIGSTKFLT","SIGSTOP","SIGSYS","SIGTERM","SIGTRAP","SIGTSTP","SIGTTIN","SIGTTOU","SIGUNUSED","SIGURG","SIGUSR1","SIGUSR2","SIGVTALRM","SIGWINCH","SIGXCPU","SIGXFSZ","SIGBREAK","SIGLOST","SIGINFO"]},{"type":"integer","minimum":1}],"default":"SIGTERM"},"displayStdout":{"type":"boolean","default":true},"displayStderr":{"type":"boolean","default":true}},"default":{}}}},"mime":{"anyOf":[{"type":"string","pattern":"^file://","format":"uri-reference"},{"type":"string","pattern":"^([^=;]+=[^/;]+/[^;]+(;|$))+$"},{"type":"object","additionalProperties":{"type":"string","pattern":"/"}}]},"stringOrRegex":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["pattern"],"properties":{"pattern":{"type":"string","format":"regex"}},"$comment":"flatten:pattern"}]},"status":{"type":"integer","minimum":200,"maximum":599}}}
1
+ {"$schema":"https://json-schema.org/draft-07/schema#","title":"Configuration for running one or more servers via the web-listener CLI","type":"object","additionalProperties":false,"required":["servers"],"properties":{"$schema":{"type":"string"},"servers":{"type":"array","items":{"anyOf":[{"$ref":"#/$defs/server"},{"$ref":"#/$defs/server-ref"}]}},"backgroundTasks":{"type":"array","items":{"$ref":"#/$defs/task"},"default":[]},"mime":{"anyOf":[{"type":"array","items":{"$ref":"#/$defs/mime"}},{"$ref":"#/$defs/mime"}],"default":[]},"writeCompressed":{"title":"Generate and save zstd, brotli, gzip, or deflate files for each served file before starting","type":"boolean","default":false},"minCompress":{"title":"The minimum number of bytes that compression must save, otherwise the file is not written","type":"integer","default":300},"noServe":{"title":"Stop after validating the configuration and optionally writing any compressed files","type":"boolean","default":false},"log":{"title":"Set the logging level. \"ready\" prints only the \"all servers ready\" line, \"progress\" prints server startup and shutdown progress.","enum":["none","ready","progress"],"default":"progress"},"logFormat":{"title":"Set the logging format. \"text\" is the most human-readable, \"json\" prints each log message as a machine-readable JSON document (one per line).","enum":["text","json"],"default":"text"}},"$defs":{"server":{"title":"Configuration for one server","type":"object","additionalProperties":false,"defaultSnippets":[{"label":"server","description":"Defines a server on a given port","body":{"port":"^${1:80}","mount":[]}}],"required":["port","mount"],"properties":{"port":{"type":"integer","minimum":1,"maximum":65535},"host":{"type":"string","default":"localhost"},"options":{"type":"object","default":{},"additionalProperties":false,"properties":{"requestTimeout":{"type":"integer"},"keepAliveTimeout":{"type":"integer"},"keepAliveTimeoutBuffer":{"type":"integer"},"connectionsCheckingInterval":{"type":"integer"},"headersTimeout":{"type":"integer"},"highWaterMark":{"type":"integer"},"maxHeaderSize":{"type":"integer"},"noDelay":{"type":"boolean"},"requireHostHeader":{"type":"boolean"},"keepAlive":{"type":"boolean"},"keepAliveInitialDelay":{"type":"integer"},"uniqueHeaders":{"type":"array","items":{"type":"string"}},"backlog":{"type":"integer","default":511},"socketTimeout":{"type":"integer"},"rejectNonStandardExpect":{"type":"boolean","default":false},"autoContinue":{"type":"boolean","default":false},"logRequests":{"type":"boolean","default":true},"restartTimeout":{"type":"integer","default":2000},"shutdownTimeout":{"type":"integer","default":500}}},"mount":{"type":"array","items":{"title":"Service to mount on the server","defaultSnippets":[{"label":"files","body":{"type":"files","path":"${1:/}","dir":"${2:.}"}},{"label":"proxy","body":{"type":"proxy","path":"${1:/}","target":"${2:http://localhost:8080}"}},{"label":"fixture","body":{"type":"fixture","method":"${1:GET}","path":"${2:/}","status":"^${3:200}","body":"$4"}},{"label":"redirect","body":{"type":"redirect","path":"$1","target":"$2","status":"^${3:307}"}},{"label":"redirect-map","body":{"type":"redirect-map","mapping":"^${1:{}}","status":"^${2:307}","options":{"caseSensitive":"^${3:false}"}}},{"label":"dependencies","body":{"type":"dependencies","path":"${1:/node_modules}","package":"${2:./package.json}"}},{"label":"delegate","body":{"type":"delegate","path":"${1:/}","config":{"file":"$2"}}},{"label":"custom","body":{"type":"custom","path":"${1:/}","method":"^${2:null}","import":"$3","namedExport":"^${4:null}"}}],"anyOf":[{"$ref":"#/$defs/mount-delegate"},{"$ref":"#/$defs/mount-headers"},{"$ref":"#/$defs/mount-files"},{"$ref":"#/$defs/mount-proxy"},{"$ref":"#/$defs/mount-fixture"},{"$ref":"#/$defs/mount-redirect"},{"$ref":"#/$defs/mount-redirect-map"},{"$ref":"#/$defs/mount-dependencies"},{"$ref":"#/$defs/mount-custom"}]}}}},"mount-delegate":{"type":"object","additionalProperties":false,"required":["type","config"],"properties":{"type":{"const":"delegate"},"path":{"type":"string","default":"/"},"config":{"$ref":"#/$defs/server-ref"},"maskSubpaths":{"type":"boolean","default":false}}},"mount-headers":{"type":"object","additionalProperties":false,"required":["type","headers"],"properties":{"type":{"const":"headers"},"path":{"type":"string","default":"/"},"headers":{"$ref":"#/$defs/headers"}}},"mount-files":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"const":"files"},"path":{"type":"string","default":"/"},"dir":{"type":"string","default":".","format":"uri-reference"},"options":{"type":"object","default":{},"additionalProperties":false,"properties":{"mode":{"enum":["dynamic","static-paths"],"default":"dynamic"},"fallback":{"type":"object","additionalProperties":false,"required":["filePath"],"properties":{"statusCode":{"$ref":"#/$defs/status","default":200},"filePath":{"type":"string"}}},"headers":{"$ref":"#/$defs/headers"},"dynamicHeaders":{"type":"array","items":{"enum":["etag","last-modified"]},"default":["etag","last-modified"]},"verbose":{"type":"boolean","default":false},"subDirectories":{"anyOf":[{"type":"boolean"},{"type":"integer","minimum":0}],"default":true},"caseSensitive":{"enum":["exact","filesystem","force-lowercase"],"default":"exact"},"allowAllDotfiles":{"type":"boolean","default":false},"allowAllTildefiles":{"type":"boolean","default":false},"allowDirectIndexAccess":{"type":"boolean","default":false},"hide":{"type":"array","items":{"$ref":"#/$defs/stringOrRegex"},"default":[]},"allow":{"type":"array","items":{"type":"string"},"default":[".well-known"]},"indexFiles":{"type":"array","items":{"type":"string"},"default":["index.htm","index.html"]},"implicitSuffixes":{"type":"array","items":{"type":"string"},"default":[]},"negotiation":{"type":"array","default":[],"items":{"type":"object","additionalProperties":false,"required":["feature","options"],"properties":{"feature":{"enum":["type","language","encoding"]},"match":{"$ref":"#/$defs/stringOrRegex"},"options":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["value","file"],"properties":{"value":{"type":"string"},"for":{"type":"string","format":"regex"},"file":{"type":"string"}}}}}}}}}}},"mount-proxy":{"type":"object","additionalProperties":false,"required":["type","target"],"properties":{"type":{"const":"proxy"},"path":{"type":"string","default":"/"},"target":{"type":"string"},"options":{"type":"object","default":{},"additionalProperties":false,"properties":{"noDelay":{"type":"boolean"},"keepAlive":{"type":"boolean"},"keepAliveInitialDelay":{"type":"integer"},"keepAliveMsecs":{"type":"integer"},"agentKeepAliveTimeoutBuffer":{"type":"integer"},"maxSockets":{"type":"integer"},"maxFreeSockets":{"type":"integer"},"timeout":{"type":"integer"},"headers":{"$ref":"#/$defs/headers"},"blockRequestHeaders":{"type":"array","items":{"type":"string"}},"blockResponseHeaders":{"type":"array","items":{"type":"string"}},"servername":{"type":"string"},"ca":{"type":"string"},"cert":{"type":"string"},"sigalgs":{"type":"string"},"ciphers":{"type":"string"},"crl":{"type":"string"},"ecdhCurve":{"type":"string"},"key":{"type":"string"},"passphrase":{"type":"string"},"pfx":{"type":"string"},"sessionTimeout":{"type":"integer"},"maxCachedSessions":{"type":"integer"}}}}},"mount-fixture":{"type":"object","additionalProperties":false,"required":["type","path","body"],"properties":{"type":{"const":"fixture"},"path":{"type":"string"},"method":{"type":"string","default":"GET"},"status":{"$ref":"#/$defs/status","default":200},"headers":{"$ref":"#/$defs/headers"},"body":{"type":"string"}}},"mount-redirect":{"type":"object","additionalProperties":false,"required":["type","path","target"],"properties":{"type":{"const":"redirect"},"path":{"type":"string"},"status":{"$ref":"#/$defs/status","default":307},"target":{"type":"string"}}},"mount-redirect-map":{"type":"object","additionalProperties":false,"required":["type","mapping"],"properties":{"type":{"const":"redirect-map"},"mapping":{"anyOf":[{"type":"object","additionalProperties":{"type":"string"}},{"type":"string","format":"uri-reference"}]},"status":{"$ref":"#/$defs/status","default":307},"options":{"type":"object","default":{},"additionalProperties":false,"properties":{"caseSensitive":{"type":"boolean","default":false}}}}},"mount-dependencies":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"const":"dependencies"},"path":{"type":"string","default":"/node_modules"},"package":{"type":"string","default":"./package.json","format":"uri-reference"},"options":{"type":"object","default":{},"additionalProperties":false,"properties":{"environment":{"type":"array","items":{"type":"string"},"default":["browser","import","production"]},"mapFilePath":{"type":"string","default":"/importmap.json"},"sourcesBasePath":{"type":"string","default":"/"},"modulesBasePath":{"type":"string","default":"/node_modules"},"mode":{"enum":["dynamic","static-paths"],"default":"dynamic"},"headers":{"$ref":"#/$defs/headers"},"dynamicHeaders":{"type":"array","items":{"enum":["etag","last-modified"]},"default":["etag","last-modified"]},"verbose":{"type":"boolean","default":false},"subDirectories":{"anyOf":[{"type":"boolean"},{"type":"integer"}],"default":true},"caseSensitive":{"enum":["exact","filesystem","force-lowercase"],"default":"exact"},"allowAllDotfiles":{"type":"boolean","default":false},"allowAllTildefiles":{"type":"boolean","default":false},"hide":{"type":"array","default":[],"items":{"$ref":"#/$defs/stringOrRegex"}},"allow":{"type":"array","items":{"type":"string"},"default":[".well-known"]}}}}},"mount-custom":{"type":"object","additionalProperties":false,"required":["type","import"],"properties":{"type":{"const":"custom"},"path":{"type":"string","default":"/"},"method":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}},{"type":"null"}],"default":null},"import":{"type":"string","format":"uri-reference"},"namedExport":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null},"maskSubpaths":{"type":"boolean","default":false}}},"headers":{"type":"object","default":{},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"array","items":{"type":"string"}}]}},"server-ref":{"type":"object","additionalProperties":false,"required":["file"],"properties":{"file":{"type":"string","format":"uri-reference"},"serverIndex":{"type":"integer","minimum":0},"serverPort":{"type":"integer","minimum":1,"maximum":65535},"includeMime":{"type":"boolean","default":true},"includeBackgroundTasks":{"type":"boolean","default":false}}},"task":{"title":"Configuration for a background task","type":"object","additionalProperties":false,"defaultSnippets":[{"label":"task","description":"Run an executable","body":{"command":"$1"}}],"required":["command"],"properties":{"command":{"anyOf":[{"type":"string","pattern":"[\\\\/]","format":"uri-reference"},{"type":"string","pattern":"^[^\\\\/]*$"}]},"arguments":{"type":"array","items":{"type":"string"},"default":[]},"cwd":{"type":"string","default":".","format":"uri-reference"},"environment":{"type":"object","additionalProperties":{"type":"string"},"default":{}},"options":{"type":"object","additionalProperties":false,"properties":{"uid":{"type":"integer","minimum":0},"gid":{"type":"integer","minimum":0},"killSignal":{"anyOf":[{"enum":["SIGABRT","SIGALRM","SIGBUS","SIGCHLD","SIGCONT","SIGFPE","SIGHUP","SIGILL","SIGINT","SIGIO","SIGIOT","SIGKILL","SIGPIPE","SIGPOLL","SIGPROF","SIGPWR","SIGQUIT","SIGSEGV","SIGSTKFLT","SIGSTOP","SIGSYS","SIGTERM","SIGTRAP","SIGTSTP","SIGTTIN","SIGTTOU","SIGUNUSED","SIGURG","SIGUSR1","SIGUSR2","SIGVTALRM","SIGWINCH","SIGXCPU","SIGXFSZ","SIGBREAK","SIGLOST","SIGINFO"]},{"type":"integer","minimum":1}],"default":"SIGTERM"},"displayStdout":{"type":"boolean","default":true},"displayStderr":{"type":"boolean","default":true}},"default":{}}}},"mime":{"anyOf":[{"type":"string","pattern":"^file://","format":"uri-reference"},{"type":"string","pattern":"^([^=;]+=[^/;]+/[^;]+(;|$))+$"},{"type":"object","additionalProperties":{"type":"string","pattern":"/"}}]},"stringOrRegex":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["pattern"],"properties":{"pattern":{"type":"string","format":"regex"}},"$comment":"flatten:pattern"}]},"status":{"type":"integer","minimum":200,"maximum":599}}}