diff --git a/test/browser/soak_e2e.js b/test/browser/soak_e2e.js index 28f7337c8..849756160 100644 --- a/test/browser/soak_e2e.js +++ b/test/browser/soak_e2e.js @@ -78,7 +78,7 @@ async function openBuilder(page) { } async function section(page, name) { - const re = new RegExp('^' + name.replace(/[/]/g, '\\/') + '$'); + const re = new RegExp('^' + name.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&') + '$'); await page.locator('div.f-s-10', { hasText: re }).first().click({ timeout: 10000 }); await settle(page); } diff --git a/test/e2e/background-asi.js b/test/e2e/background-asi.js index 6aeab6c4d..264597c2d 100644 --- a/test/e2e/background-asi.js +++ b/test/e2e/background-asi.js @@ -10,15 +10,15 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http=require('http'),fs=require('fs'),path=require('path'); +const path=require('path'); +const { createStaticServer } = require('./static-server'); const REPO=process.env.REPO||path.resolve(__dirname,'../..'); const ROOT=path.join(REPO,'resources/public'), PORT=Number(process.env.PORT||8862); const HOST=`
`; -const mime={'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png','.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf','.map':'application/json'}; -const server=http.createServer((req,res)=>{const p=decodeURIComponent(req.url.split('?')[0]);const R=path.resolve(ROOT),fp=path.resolve(R,'.'+p);if(fp!==R&&!fp.startsWith(R+path.sep)){res.statusCode=404;return res.end();}fs.readFile(fp,(e,d)=>{if(e){res.setHeader('Content-Type','text/html');res.end(HOST);return;}res.setHeader('Content-Type',mime[path.extname(fp)]||'application/octet-stream');res.end(d);});}); +const server=createStaticServer(ROOT,HOST); const PLUGINS=`{"Default Option Source" {} "BG Pack" {:orcpub.dnd.e5/backgrounds {:tide-born {:name "Tide-Born" :option-pack "BG Pack" :key :tide-born :ability-increases [[2 :cha] [1 :martial]]}}}}`; const errs=[]; const U=`http://localhost:${PORT}`; const results=[]; const check=(n,ok,extra)=>{results.push(ok);console.log(` ${ok?'PASS':'FAIL'} ${n}${extra?' '+extra:''}`);}; diff --git a/test/e2e/exact-spread-asi.js b/test/e2e/exact-spread-asi.js index 9e2378caa..503c56ebb 100644 --- a/test/e2e/exact-spread-asi.js +++ b/test/e2e/exact-spread-asi.js @@ -11,15 +11,15 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http=require('http'),fs=require('fs'),path=require('path'); +const path=require('path'); +const { createStaticServer } = require('./static-server'); const REPO=process.env.REPO||path.resolve(__dirname,'../..'); const ROOT=path.join(REPO,'resources/public'), PORT=Number(process.env.PORT||8842); const HOST=`
`; -const mime={'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png','.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf','.map':'application/json'}; -const server=http.createServer((req,res)=>{const p=decodeURIComponent(req.url.split('?')[0]);const R=path.resolve(ROOT),fp=path.resolve(R,'.'+p);if(fp!==R&&!fp.startsWith(R+path.sep)){res.statusCode=404;return res.end();}fs.readFile(fp,(e,d)=>{if(e){res.setHeader('Content-Type','text/html');res.end(HOST);return;}res.setHeader('Content-Type',mime[path.extname(fp)]||'application/octet-stream');res.end(d);});}); +const server=createStaticServer(ROOT,HOST); // :from :martial restricts the floating pool to str/dex/con (also guards pool-restriction) const PLUGINS=`{"Default Option Source" {} "Bag Pack" {:orcpub.dnd.e5/races {:bag-touched {:size :medium, :speed 30, :languages #{}, :traits [], :name "Bag Touched", :option-pack "Bag Pack", :key :bag-touched, :ability-increases [[2 :martial] [1 :martial]]}}}}`; const errs=[]; const U=`http://localhost:${PORT}`; diff --git a/test/e2e/export-import-use.js b/test/e2e/export-import-use.js index f8b0f3b1a..fbfeaaf21 100644 --- a/test/e2e/export-import-use.js +++ b/test/e2e/export-import-use.js @@ -22,15 +22,15 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http=require('http'),fs=require('fs'),path=require('path'); +const fs=require('fs'),path=require('path'); +const { createStaticServer } = require('./static-server'); const REPO=process.env.REPO||path.resolve(__dirname,'../..'); const ROOT=path.join(REPO,'resources/public'), PORT=Number(process.env.PORT||8831); const HOST=`
`; -const mime={'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png','.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf','.map':'application/json'}; -const server=http.createServer((req,res)=>{const p=decodeURIComponent(req.url.split('?')[0]);const R=path.resolve(ROOT),fp=path.resolve(R,'.'+p);if(fp!==R&&!fp.startsWith(R+path.sep)){res.statusCode=404;return res.end();}fs.readFile(fp,(e,d)=>{if(e){res.setHeader('Content-Type','text/html');res.end(HOST);return;}res.setHeader('Content-Type',mime[path.extname(fp)]||'application/octet-stream');res.end(d);});}); +const server=createStaticServer(ROOT,HOST); const errs=[]; const U=`http://localhost:${PORT}`; const lsPlugins=async pg=>pg.evaluate(()=>localStorage.getItem('plugins')||''); const results=[]; const check=(name,ok)=>{results.push(ok);console.log(` ${ok?'PASS':'FAIL'} ${name}`);}; diff --git a/test/e2e/ignored-entry-note.js b/test/e2e/ignored-entry-note.js index 1d5e224b3..8f212ce92 100644 --- a/test/e2e/ignored-entry-note.js +++ b/test/e2e/ignored-entry-note.js @@ -8,12 +8,12 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http=require('http'),fs=require('fs'),path=require('path'); +const path=require('path'); +const { createStaticServer } = require('./static-server'); const REPO=process.env.REPO||path.resolve(__dirname,'../..'); const ROOT=path.join(REPO,'resources/public'), PORT=Number(process.env.PORT||8889); const HOST=`
`; -const mime={'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png','.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf'}; -const server=http.createServer((req,res)=>{const p=decodeURIComponent(req.url.split('?')[0]);const R=path.resolve(ROOT),fp=path.resolve(R,'.'+p);if(fp!==R&&!fp.startsWith(R+path.sep)){res.statusCode=404;return res.end();}fs.readFile(fp,(e,d)=>{if(e){res.setHeader('Content-Type','text/html');res.end(HOST);return;}res.setHeader('Content-Type',mime[path.extname(fp)]||'application/octet-stream');res.end(d);});}); +const server=createStaticServer(ROOT,HOST); const PLUGINS=`{"Default Option Source" {} "BrokenPack" {:orcpub.dnd.e5/races {:broken {:name "BrokenRace" :key :broken :option-pack "BrokenPack" :size :medium :speed 30 :languages #{} :traits [] :ability-increases [[1 :cha] "junk" [:bad]]}}}}`; const U=`http://localhost:${PORT}`; const results=[]; const check=(n,ok,x)=>{results.push(ok);console.log(` ${ok?'PASS':'FAIL'} ${n}${x?' '+x:''}`);}; (async()=>{ diff --git a/test/e2e/messy-pak-survives.js b/test/e2e/messy-pak-survives.js index c379e23bc..773db5604 100644 --- a/test/e2e/messy-pak-survives.js +++ b/test/e2e/messy-pak-survives.js @@ -8,12 +8,12 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http=require('http'),fs=require('fs'),path=require('path'); +const path=require('path'); +const { createStaticServer } = require('./static-server'); const REPO=process.env.REPO||path.resolve(__dirname,'../..'); const ROOT=path.join(REPO,'resources/public'), PORT=Number(process.env.PORT||8886); const HOST=`
`; -const mime={'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png','.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf'}; -const server=http.createServer((req,res)=>{const p=decodeURIComponent(req.url.split('?')[0]);const R=path.resolve(ROOT),fp=path.resolve(R,'.'+p);if(fp!==R&&!fp.startsWith(R+path.sep)){res.statusCode=404;return res.end();}fs.readFile(fp,(e,d)=>{if(e){res.setHeader('Content-Type','text/html');res.end(HOST);return;}res.setHeader('Content-Type',mime[path.extname(fp)]||'application/octet-stream');res.end(d);});}); +const server=createStaticServer(ROOT,HOST); // one well-formed race + one with deliberately malformed ASI/save data (valid EDN, junk VALUES) const PLUGINS=`{"Default Option Source" {} "Messy Pak" {:orcpub.dnd.e5/races {` +`:warden {:name "Warden" :key :warden :option-pack "Messy Pak" :size :medium :speed 30 :languages #{} :traits [] :ability-increases [[1 :martial :save]] :save-proficiencies [[1 :mental]]} ` diff --git a/test/e2e/multi-container-asi.js b/test/e2e/multi-container-asi.js index 14ec5c67f..04261fff0 100644 --- a/test/e2e/multi-container-asi.js +++ b/test/e2e/multi-container-asi.js @@ -9,12 +9,12 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http=require('http'),fs=require('fs'),path=require('path'); +const path=require('path'); +const { createStaticServer } = require('./static-server'); const REPO=process.env.REPO||path.resolve(__dirname,'../..'); const ROOT=path.join(REPO,'resources/public'), PORT=Number(process.env.PORT||8875); const HOST=`
`; -const mime={'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png','.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf'}; -const server=http.createServer((req,res)=>{const p=decodeURIComponent(req.url.split('?')[0]);const R=path.resolve(ROOT),fp=path.resolve(R,'.'+p);if(fp!==R&&!fp.startsWith(R+path.sep)){res.statusCode=404;return res.end();}fs.readFile(fp,(e,d)=>{if(e){res.setHeader('Content-Type','text/html');res.end(HOST);return;}res.setHeader('Content-Type',mime[path.extname(fp)]||'application/octet-stream');res.end(d);});}); +const server=createStaticServer(ROOT,HOST); // race + background each grant +1 to a martial stat (separate containers, same pool) const PLUGINS=`{"Default Option Source" {} "Pack" {:orcpub.dnd.e5/races {:tide {:name "Tide" :key :tide :option-pack "Pack" :size :medium :speed 30 :languages #{} :traits [] :ability-increases [[1 :martial]]}} :orcpub.dnd.e5/backgrounds {:sea {:name "Sea-Marked" :key :sea :option-pack "Pack" :ability-increases [[1 :martial]]}}}}`; const U=`http://localhost:${PORT}`; const results=[]; const check=(n,ok,x)=>{results.push(ok);console.log(` ${ok?'PASS':'FAIL'} ${n}${x?' '+x:''}`);}; diff --git a/test/e2e/multi-feat-floating.js b/test/e2e/multi-feat-floating.js index 3914149ba..d5373da58 100644 --- a/test/e2e/multi-feat-floating.js +++ b/test/e2e/multi-feat-floating.js @@ -9,12 +9,12 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http=require('http'),fs=require('fs'),path=require('path'); +const path=require('path'); +const { createStaticServer } = require('./static-server'); const REPO=process.env.REPO||path.resolve(__dirname,'../..'); const ROOT=path.join(REPO,'resources/public'), PORT=Number(process.env.PORT||8888); const HOST=`
`; -const mime={'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png','.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf'}; -const server=http.createServer((req,res)=>{const p=decodeURIComponent(req.url.split('?')[0]);const R=path.resolve(ROOT),fp=path.resolve(R,'.'+p);if(fp!==R&&!fp.startsWith(R+path.sep)){res.statusCode=404;return res.end();}fs.readFile(fp,(e,d)=>{if(e){res.setHeader('Content-Type','text/html');res.end(HOST);return;}res.setHeader('Content-Type',mime[path.extname(fp)]||'application/octet-stream');res.end(d);});}); +const server=createStaticServer(ROOT,HOST); const PLUGINS=`{"Default Option Source" {} "M" {` +`:orcpub.dnd.e5/races {:ra {:name "Plainfolk" :key :ra :option-pack "M" :size :medium :speed 30 :languages #{} :traits []}} ` +`:orcpub.dnd.e5/feats {` diff --git a/test/e2e/multi-source-floating-attribution.js b/test/e2e/multi-source-floating-attribution.js index f5f8b6992..5cb42c6ba 100644 --- a/test/e2e/multi-source-floating-attribution.js +++ b/test/e2e/multi-source-floating-attribution.js @@ -11,12 +11,12 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http=require('http'),fs=require('fs'),path=require('path'); +const path=require('path'); +const { createStaticServer } = require('./static-server'); const REPO=process.env.REPO||path.resolve(__dirname,'../..'); const ROOT=path.join(REPO,'resources/public'), PORT=Number(process.env.PORT||8887); const HOST=`
`; -const mime={'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png','.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf'}; -const server=http.createServer((req,res)=>{const p=decodeURIComponent(req.url.split('?')[0]);const R=path.resolve(ROOT),fp=path.resolve(R,'.'+p);if(fp!==R&&!fp.startsWith(R+path.sep)){res.statusCode=404;return res.end();}fs.readFile(fp,(e,d)=>{if(e){res.setHeader('Content-Type','text/html');res.end(HOST);return;}res.setHeader('Content-Type',mime[path.extname(fp)]||'application/octet-stream');res.end(d);});}); +const server=createStaticServer(ROOT,HOST); const PLUGINS=`{"Default Option Source" {} "M" {` +`:orcpub.dnd.e5/races {:ra {:name "RaceA" :key :ra :option-pack "M" :size :medium :speed 30 :languages #{} :traits [] :ability-increases [[2 :any] [1 :any]]}} ` +`:orcpub.dnd.e5/subraces {:sa {:name "SubA" :key :sa :race :ra :option-pack "M" :traits [] :ability-increases [[2 :any] [1 :any]]}} ` diff --git a/test/e2e/race-builder-asi.js b/test/e2e/race-builder-asi.js index 9f64639de..d92ee6ce2 100644 --- a/test/e2e/race-builder-asi.js +++ b/test/e2e/race-builder-asi.js @@ -18,7 +18,8 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http = require('http'), fs = require('fs'), path = require('path'); +const path = require('path'); +const { createStaticServer } = require('./static-server'); const REPO = process.env.REPO || path.resolve(__dirname, '../..'); const ROOT = path.join(REPO, 'resources/public'); @@ -27,20 +28,8 @@ const HOST = `
`; -const mime = {'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png', - '.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf','.map':'application/json'}; - // Static server rooted at resources/public, with SPA fallback (deep route -> host page). -const server = http.createServer((req, res) => { - const p = decodeURIComponent(req.url.split('?')[0]); - const root = path.resolve(ROOT), fp = path.resolve(root, '.' + p); - if (fp !== root && !fp.startsWith(root + path.sep)) { res.statusCode = 404; return res.end(); } - fs.readFile(fp, (e, d) => { - if (e) { res.setHeader('Content-Type', 'text/html'); res.end(HOST); return; } - res.setHeader('Content-Type', mime[path.extname(fp)] || 'application/octet-stream'); - res.end(d); - }); -}); +const server = createStaticServer(ROOT, HOST); (async () => { await new Promise(r => server.listen(PORT, '127.0.0.1', r)); diff --git a/test/e2e/save-grants-authoring.js b/test/e2e/save-grants-authoring.js index 07fa9e9a6..acd59a763 100644 --- a/test/e2e/save-grants-authoring.js +++ b/test/e2e/save-grants-authoring.js @@ -13,15 +13,15 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http=require('http'),fs=require('fs'),path=require('path'); +const path=require('path'); +const { createStaticServer } = require('./static-server'); const REPO=process.env.REPO||path.resolve(__dirname,'../..'); const ROOT=path.join(REPO,'resources/public'), PORT=Number(process.env.PORT||8881); const HOST=`
`; -const mime={'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png','.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf','.map':'application/json'}; -const server=http.createServer((req,res)=>{const p=decodeURIComponent(req.url.split('?')[0]);const R=path.resolve(ROOT),fp=path.resolve(R,'.'+p);if(fp!==R&&!fp.startsWith(R+path.sep)){res.statusCode=404;return res.end();}fs.readFile(fp,(e,d)=>{if(e){res.setHeader('Content-Type','text/html');res.end(HOST);return;}res.setHeader('Content-Type',mime[path.extname(fp)]||'application/octet-stream');res.end(d);});}); +const server=createStaticServer(ROOT,HOST); const errs=[]; const U=`http://localhost:${PORT}`; const results=[]; const check=(n,ok,x)=>{results.push(ok);console.log(` ${ok?'PASS':'FAIL'} ${n}${x?' '+x:''}`);}; const lsPlugins=async pg=>pg.evaluate(()=>localStorage.getItem('plugins')||''); diff --git a/test/e2e/save-grants-use.js b/test/e2e/save-grants-use.js index 90177e08c..6af311195 100644 --- a/test/e2e/save-grants-use.js +++ b/test/e2e/save-grants-use.js @@ -13,12 +13,12 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http=require('http'),fs=require('fs'),path=require('path'); +const path=require('path'); +const { createStaticServer } = require('./static-server'); const REPO=process.env.REPO||path.resolve(__dirname,'../..'); const ROOT=path.join(REPO,'resources/public'), PORT=Number(process.env.PORT||8883); const HOST=`
`; -const mime={'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png','.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf'}; -const server=http.createServer((req,res)=>{const p=decodeURIComponent(req.url.split('?')[0]);const R=path.resolve(ROOT),fp=path.resolve(R,'.'+p);if(fp!==R&&!fp.startsWith(R+path.sep)){res.statusCode=404;return res.end();}fs.readFile(fp,(e,d)=>{if(e){res.setHeader('Content-Type','text/html');res.end(HOST);return;}res.setHeader('Content-Type',mime[path.extname(fp)]||'application/octet-stream');res.end(d);});}); +const server=createStaticServer(ROOT,HOST); const PLUGINS=`{"Default Option Source" {} "SavePack" {:orcpub.dnd.e5/races {:warden {:name "Warden" :key :warden :option-pack "SavePack" :size :medium :speed 30 :languages #{} :traits [] :ability-increases [[1 :martial :save]] :save-proficiencies [[1 :mental]]}}}}`; const U=`http://localhost:${PORT}`; const results=[]; const check=(n,ok,x)=>{results.push(ok);console.log(` ${ok?'PASS':'FAIL'} ${n}${x?' '+x:''}`);}; // proficient? read the live Saving Throws summary table (.last() = the live panel; a proficient row is diff --git a/test/e2e/static-server.js b/test/e2e/static-server.js new file mode 100644 index 000000000..c3df8eb9b --- /dev/null +++ b/test/e2e/static-server.js @@ -0,0 +1,39 @@ +'use strict'; +// Static file server for the browser scripts in this folder: serves a built app from a root +// directory, with the single-page-app fallback (anything not on disk gets the host page). +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +const MIME = { + '.js': 'text/javascript', '.css': 'text/css', '.html': 'text/html', '.png': 'image/png', + '.svg': 'image/svg+xml', '.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf', + '.map': 'application/json' +}; + +/** + * Builds an http.Server that serves files under `root`. + * `hostPage` is the HTML sent for the root URL and for any path with no file behind it. + * A request that resolves outside `root` (or carries a NUL byte or bad %-encoding) gets + * an empty 404 and never reaches the filesystem. + * Returns the server; the caller calls `.listen`. + */ +function createStaticServer(root, hostPage) { + const base = path.resolve(root); + const sendHost = res => { res.setHeader('Content-Type', 'text/html'); res.end(hostPage); }; + return http.createServer((req, res) => { + let requested; + try { requested = decodeURIComponent(req.url.split('?')[0]); } catch (e) { requested = null; } + if (requested === null || requested.includes('\0')) { res.statusCode = 404; return res.end(); } + const file = path.resolve(base, '.' + requested); + if (file === base) return sendHost(res); + if (!file.startsWith(base + path.sep)) { res.statusCode = 404; return res.end(); } + fs.readFile(file, (err, data) => { + if (err) return sendHost(res); + res.setHeader('Content-Type', MIME[path.extname(file)] || 'application/octet-stream'); + res.end(data); + }); + }); +} + +module.exports = { createStaticServer }; diff --git a/test/e2e/subclass-asi-toggle.js b/test/e2e/subclass-asi-toggle.js index 49eafe13c..e15b96fdb 100644 --- a/test/e2e/subclass-asi-toggle.js +++ b/test/e2e/subclass-asi-toggle.js @@ -10,15 +10,15 @@ const { chromium } = require('playwright'); // Playwright's own chromium is not installed here; lib.js finds the preinstalled one. const { findChrome, dismissWhatsNew } = require('./lib'); -const http=require('http'),fs=require('fs'),path=require('path'); +const path=require('path'); +const { createStaticServer } = require('./static-server'); const REPO=process.env.REPO||path.resolve(__dirname,'../..'); const ROOT=path.join(REPO,'resources/public'), PORT=Number(process.env.PORT||8868); const HOST=`
`; -const mime={'.js':'text/javascript','.css':'text/css','.html':'text/html','.png':'image/png','.svg':'image/svg+xml','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf','.map':'application/json'}; -const server=http.createServer((req,res)=>{const p=decodeURIComponent(req.url.split('?')[0]);const R=path.resolve(ROOT),fp=path.resolve(R,'.'+p);if(fp!==R&&!fp.startsWith(R+path.sep)){res.statusCode=404;return res.end();}fs.readFile(fp,(e,d)=>{if(e){res.setHeader('Content-Type','text/html');res.end(HOST);return;}res.setHeader('Content-Type',mime[path.extname(fp)]||'application/octet-stream');res.end(d);});}); +const server=createStaticServer(ROOT,HOST); const errs=[]; const U=`http://localhost:${PORT}`; const results=[]; const check=(n,ok,extra)=>{results.push(ok);console.log(` ${ok?'PASS':'FAIL'} ${n}${extra?' '+extra:''}`);}; (async()=>{