From e4433ac13f4d13163dc7218af9b0b49f2279400e Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Fri, 8 May 2020 15:13:43 +0100 Subject: [PATCH 01/14] Adds --module flag, updates test and readme --- README.md | 12 ++++++---- cli.js | 1 + servor.js | 13 ++++++++--- test/assets/index.css | 2 +- test/assets/index.js | 2 +- test/index.html | 8 ++++--- test/index.js | 53 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 test/index.js diff --git a/README.md b/README.md index c28fc1c..8926c18 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,12 @@ Servør can be invoked via the command line or programmatically using the node A The motivation here was to write a package from the ground up with no dependencies; using only native node and browser APIs to do a specific task with minimal code. - 🗂 Serves static content like scripts, styles, images from a given directory -- 🗜 Uses gzip on common filetypes like html, css and js to give a production feel - ♻️ Reloads the browser when project files get added, removed or modified +- 🗜 Uses gzip on common filetypes like html, css, js and json - 🔐 Supports https and http2 with trusted self signed certificates - 🖥 Redirects all path requests to a single file for frontend routing -- 🔎 Discovers freely available ports to serve on if no port is specified +- 📦 Accepts both HTML and JavaScript files as the root file for a directory +- 🔎 Discovers freely available ports to start on by default ## CLI Usage @@ -38,7 +39,8 @@ Optional flags passed as non-positional arguments: - `--browse` causes the browser to open when the server starts - `--reload` causes the browser to reload when files change - `--secure` starts the server with https using generated credentials -- `--silent` prevents the node process from logging to stdout +- `--silent` prevents the server node process from logging to stdout +- `--module` causes the server to wrap the root in script type module tags Example usage with npm scripts in a `package.json` file after running `npm i servor -D`: @@ -55,7 +57,7 @@ Example usage with npm scripts in a `package.json` file after running `npm i ser ### Generating Credentials -> NOTE: This process depends on the `openssl` command existing (tested on macOS only) +> NOTE: This process depends on the `openssl` command existing (tested on macOS and linux only) The files `servor.crt` and `servor.key` need to exist for the server to start using https. If the files do not exist when the `--secure` flag is passed, then [`certify.sh`](/certify.sh) is invoked which: @@ -101,7 +103,7 @@ const { url, root, protocol, port, ips } = await servor(config); ### Inject -The `inject` property accepts a string that gets prepended to the servers root document (which is `index.html` by default). This could be used to inject config or extend the development servers behavior and capabilities to suit specific environments. +The `inject` property accepts a string that gets appended to the servers root document (which is `index.html` by default). This could be used to inject config or extend the development servers behavior and capabilities to suit specific environments. ```js const config = require('package.json'); diff --git a/cli.js b/cli.js index cd16236..cf8d17a 100755 --- a/cli.js +++ b/cli.js @@ -51,6 +51,7 @@ const open = fallback: args[1], port: args[2], reload: !!~process.argv.indexOf('--reload'), + module: !!~process.argv.indexOf('--module'), credentials, }); diff --git a/servor.js b/servor.js index a41b7cf..c2b043d 100644 --- a/servor.js +++ b/servor.js @@ -44,7 +44,8 @@ const mimes = Object.entries(require('./types.json')).reduce( module.exports = async ({ root = '.', - fallback = 'index.html', + module = false, + fallback = module ? 'index.js' : 'index.html', port, reload = true, inject = '', @@ -87,6 +88,8 @@ module.exports = async ({ // Server utility functions + const utf8 = (file) => Buffer.from(file, 'binary').toString('utf8'); + const sendError = (res, status) => { res.writeHead(status); res.end(); @@ -96,7 +99,7 @@ module.exports = async ({ if (['js', 'css', 'html', 'json', 'xml', 'svg'].includes(ext)) { res.removeHeader('Content-Length'); res.setHeader('Content-Encoding', 'gzip'); - file = zlib.gzipSync(Buffer.from(file, 'binary').toString('utf8')); + file = zlib.gzipSync(utf8(file)); encoding = 'utf8'; } res.writeHead(status, { @@ -133,11 +136,15 @@ module.exports = async ({ const status = isRoute && pathname !== '/' ? 301 : 200; const resource = isRoute ? `/${fallback}` : decodeURI(pathname); const uri = path.join(root, resource); - const ext = uri.replace(/^.*[\.\/\\]/, '').toLowerCase(); + let ext = uri.replace(/^.*[\.\/\\]/, '').toLowerCase(); fs.stat(uri, (err) => { if (err) return sendError(res, 404); fs.readFile(uri, 'binary', (err, file) => { if (err) return sendError(res, 500); + if (isRoute && module) { + file = ``; + ext = 'html'; + } if (isRoute) file = file + inject + livereload; sendFile(res, status, file, ext); }); diff --git a/test/assets/index.css b/test/assets/index.css index 929336c..b5de111 100644 --- a/test/assets/index.css +++ b/test/assets/index.css @@ -9,7 +9,7 @@ -webkit-tap-highlight-color: transparent; } -body { +main { font-family: 'Roboto', sans-serif; font-size: 16px; background: #212121; diff --git a/test/assets/index.js b/test/assets/index.js index ca8e8e0..366b252 100644 --- a/test/assets/index.js +++ b/test/assets/index.js @@ -1,2 +1,2 @@ var $text = document.querySelector('h2'); -$text.innerHTML = window.location +$text.innerHTML = window.location; diff --git a/test/index.html b/test/index.html index 76e5591..095700b 100644 --- a/test/index.html +++ b/test/index.html @@ -7,8 +7,10 @@ - File that exists -

servør

-

+
+ File that exists +

servør

+

+
diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..a636ff1 --- /dev/null +++ b/test/index.js @@ -0,0 +1,53 @@ +import { react, html, css } from 'https://unpkg.com/rplus'; + +const style = css` + font-family: 'Roboto', sans-serif; + font-size: 16px; + background: #212121; + color: #f2f2f2; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + :global(*) { + display: block; + flex: none; + margin: 0; + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; + } + + :global(head), + :global(script), + :global(style) { + display: none; + } + + img { + width: 30vmin; + opacity: 0.38; + } + + h1 { + font-size: 20vmin; + } + + h2 { + color: #6f6f6f; + font-size: 4vmin; + font-weight: normal; + } +`; + +react.render( + html` +
+ File that exists +

servør

+

${location.href}

+
+ `, + document.body +); From f42ca8710887db6c65385a02136dc494b386d020 Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Sat, 9 May 2020 13:38:52 +0100 Subject: [PATCH 02/14] Merge routes and module flag logic --- cli.js | 1 + servor.js | 19 +++++++++--- test/nested/assets/exists.png | Bin 0 -> 788 bytes test/nested/assets/index.css | 46 +++++++++++++++++++++++++++++ test/nested/assets/index.js | 2 ++ test/nested/index.html | 20 +++++++++++++ test/nested/index.js | 53 ++++++++++++++++++++++++++++++++++ 7 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 test/nested/assets/exists.png create mode 100644 test/nested/assets/index.css create mode 100644 test/nested/assets/index.js create mode 100644 test/nested/index.html create mode 100644 test/nested/index.js diff --git a/cli.js b/cli.js index cf8d17a..dfba48e 100755 --- a/cli.js +++ b/cli.js @@ -52,6 +52,7 @@ const open = port: args[2], reload: !!~process.argv.indexOf('--reload'), module: !!~process.argv.indexOf('--module'), + routes: !!~process.argv.indexOf('--routes'), credentials, }); diff --git a/servor.js b/servor.js index c2b043d..a1b5613 100644 --- a/servor.js +++ b/servor.js @@ -48,6 +48,7 @@ module.exports = async ({ fallback = module ? 'index.js' : 'index.html', port, reload = true, + routes = false, inject = '', credentials, } = {}) => { @@ -115,6 +116,9 @@ module.exports = async ({ res.write('\n\n'); }; + const indexFileExists = (pathname) => + fs.existsSync(`${root}${pathname}/${fallback}`); + const isRouteRequest = (pathname) => !~pathname.split('/').pop().indexOf('.'); // Start the server on the desired port @@ -133,19 +137,26 @@ module.exports = async ({ clients.push(res); } else { const isRoute = isRouteRequest(pathname); + const hasRoute = isRoute && routes && indexFileExists(pathname); const status = isRoute && pathname !== '/' ? 301 : 200; - const resource = isRoute ? `/${fallback}` : decodeURI(pathname); + const resource = isRoute + ? hasRoute + ? `/${decodeURI(pathname)}/${fallback}` + : `/${fallback}` + : decodeURI(pathname); const uri = path.join(root, resource); let ext = uri.replace(/^.*[\.\/\\]/, '').toLowerCase(); fs.stat(uri, (err) => { if (err) return sendError(res, 404); fs.readFile(uri, 'binary', (err, file) => { if (err) return sendError(res, 500); - if (isRoute && module) { - file = ``; + if (isRoute) { + const base = path.join('/', pathname, '/'); + const doc = ``; + if (module) file = ``; + file = doc + file + inject + livereload; ext = 'html'; } - if (isRoute) file = file + inject + livereload; sendFile(res, status, file, ext); }); }); diff --git a/test/nested/assets/exists.png b/test/nested/assets/exists.png new file mode 100644 index 0000000000000000000000000000000000000000..e1771b897f12081444a18b3aea4b89fa455470b8 GIT binary patch literal 788 zcmV+v1MB>WP)dqUe83!Dh4SJRvws!pi`P&8 zD%>vK{#E#2yu+(-xOn}m@V|KfSK)i{`d8t9@&2zufARjW!vErZUxlP=SwKR|0vVby^jT}&>11OmXxat#-5EW1TpoFOS=?5l6#$O+x)F=TJI=~^oU*93XLEj<3 zQC}b6ukR4xudfgA*LMi;*VhMZ8S9^qrN2dFex*oXSS2#c$S#|p>ZC7xCo*4=x*G-x zR9*Ci1tRkvsk3_k0SPwx!VHm_4$!ZkV5cvuVOEh~tN)Hz+(8rxcKX5~ zvB>d5kzl7^p_Q17A&LYWePM*y3&_K0@oV(Wk66nn@4p2yOEMk{+xKP$@9=_Mr`h z?JEC;0h&lH`? + + + + Web Application + + + + + + + + File that exists +

servør

+

+ + diff --git a/test/nested/index.js b/test/nested/index.js new file mode 100644 index 0000000..926f40a --- /dev/null +++ b/test/nested/index.js @@ -0,0 +1,53 @@ +import { react, html, css } from 'https://unpkg.com/rplus'; + +const style = css` + font-family: 'Roboto', sans-serif; + font-size: 16px; + background: #333; + color: #f2f2f2; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + :global(*) { + display: block; + flex: none; + margin: 0; + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; + } + + :global(head), + :global(script), + :global(style) { + display: none; + } + + img { + width: 30vmin; + opacity: 0.38; + } + + h1 { + font-size: 20vmin; + } + + h2 { + color: #6f6f6f; + font-size: 4vmin; + font-weight: normal; + } +`; + +react.render( + html` +
+ File that exists +

servør

+

${location.href}

+
+ `, + document.body +); From 59e138b26f47ff88b9602008df1c55deb3a2575f Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Sun, 10 May 2020 23:58:24 +0100 Subject: [PATCH 03/14] Merges --routes and -- flags then adds tests --- .gitignore | 3 +- package.json | 8 +- servor.js | 60 ++++---- test.js | 139 ++++++++++++++++++ test/index.html | 2 +- test/index.js | 2 + test/nested/index.html | 6 +- test/nested/index.js | 2 + yarn.lock | 315 +++++++++++++++++++++++++++++++++++++++++ 9 files changed, 499 insertions(+), 38 deletions(-) create mode 100644 test.js create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index d320f09..6f2aee4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ *.pem *.crt -.DS_Store \ No newline at end of file +.DS_Store +node_modules \ No newline at end of file diff --git a/package.json b/package.json index 5a0dc08..d00e628 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,11 @@ ], "scripts": { "cleanup": "rm -f servor.key servor.crt", - "test": "node cli test --browse --reload", - "test:secure": "yarn test --secure" + "test": "npm run cleanup && node test.js" }, "author": "Luke Jackson ", - "license": "MIT" + "license": "MIT", + "devDependencies": { + "puppeteer": "^3.0.4" + } } diff --git a/servor.js b/servor.js index a1b5613..1e7f237 100644 --- a/servor.js +++ b/servor.js @@ -92,7 +92,8 @@ module.exports = async ({ const utf8 = (file) => Buffer.from(file, 'binary').toString('utf8'); const sendError = (res, status) => { - res.writeHead(status); + res.writeHead(status, { 'Access-Control-Allow-Origin': '*' }); + res.write(`${status}`); res.end(); }; @@ -121,44 +122,47 @@ module.exports = async ({ const isRouteRequest = (pathname) => !~pathname.split('/').pop().indexOf('.'); + const registerClient = (res) => { + res.writeHead(200, { + Connection: 'keep-alive', + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Access-Control-Allow-Origin': '*', + }); + sendMessage(res, 'connected', 'ready'); + setInterval(sendMessage, 60000, res, 'ping', 'waiting'); + clients.push(res); + }; + // Start the server on the desired port server((req, res) => { const pathname = url.parse(req.url).pathname; - if (reload && pathname === '/livereload') { - res.writeHead(200, { - Connection: 'keep-alive', - 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', - 'Access-Control-Allow-Origin': '*', - }); - sendMessage(res, 'connected', 'ready'); - setInterval(sendMessage, 60000, res, 'ping', 'waiting'); - clients.push(res); - } else { + if (reload && pathname === '/livereload') registerClient(res); + else { const isRoute = isRouteRequest(pathname); - const hasRoute = isRoute && routes && indexFileExists(pathname); - const status = isRoute && pathname !== '/' ? 301 : 200; + const hasIndex = isRoute && routes && indexFileExists(pathname); + const status = !isRoute || hasIndex || pathname === '/' ? 200 : 301; + console.log(status, pathname, !isRoute || hasIndex || pathname === '/'); + const resource = isRoute - ? hasRoute + ? hasIndex ? `/${decodeURI(pathname)}/${fallback}` : `/${fallback}` : decodeURI(pathname); const uri = path.join(root, resource); let ext = uri.replace(/^.*[\.\/\\]/, '').toLowerCase(); - fs.stat(uri, (err) => { - if (err) return sendError(res, 404); - fs.readFile(uri, 'binary', (err, file) => { - if (err) return sendError(res, 500); - if (isRoute) { - const base = path.join('/', pathname, '/'); - const doc = ``; - if (module) file = ``; - file = doc + file + inject + livereload; - ext = 'html'; - } - sendFile(res, status, file, ext); - }); + if (!fs.existsSync(uri)) return sendError(res, 404); + fs.readFile(uri, 'binary', (err, file) => { + if (err) return sendError(res, 500); + if (isRoute) { + const base = path.join('/', pathname, '/'); + const doc = ``; + if (module) file = ``; + file = doc + file + inject + livereload; + ext = 'html'; + } + sendFile(res, status, file, ext); }); } }).listen(parseInt(port, 10)); diff --git a/test.js b/test.js new file mode 100644 index 0000000..e7d1de2 --- /dev/null +++ b/test.js @@ -0,0 +1,139 @@ +const fs = require('fs'); +const puppeteer = require('puppeteer'); +const cp = require('child_process'); + +const matches = (obj, source) => + Object.keys(source).every( + (key) => + obj.hasOwnProperty(key) && + JSON.stringify(obj[key]) === JSON.stringify(source[key]) + ); + +const test = (cmd) => (url) => (expect) => async () => { + // Make sure nothing is running on port 8080 + cp.execSync( + "lsof -n -i4TCP:8080 | grep LISTEN | awk '{ print $2 }' | xargs kill" + ); + + // Run the command and wait for the server to start + const [c, ...a] = cmd.split(' '); + const servor = cp.spawn(c, a); + await new Promise((resolve) => servor.stdout.on('data', resolve)); + + const browser = await puppeteer.launch({ + ignoreHTTPSErrors: true, + headless: true, + slowMo: 0, + }); + + // Load new page and go to url + const page = await browser.newPage(); + await page.setCacheEnabled(false); + + const res = await page.goto(url); + + // Collect data from response and page + const status = res.status(); + const headers = res.headers(); + const content = await page.content(); + + // Change a file to trigger reload + let reload = false; + if (cmd.includes('--reload')) { + fs.readFile('test/index.html', 'utf-8', (_, data) => { + fs.writeFileSync('test/index.html', data, 'utf-8'); + }); + reload = await page.waitForNavigation({ timeout: 1000 }).catch(() => false); + } + + const result = { + status, + reload: !!reload, + gzip: headers['content-encoding'] === 'gzip', + cors: headers['access-control-allow-origin'] === '*', + includes: [ + 'SERVOR_TEST_INDEX', + 'SERVOR_TEST_NESTED_INDEX', + 'SERVOR_TEST_MODULE_INDEX', + 'SERVOR_TEST_NESTED_MODULE_INDEX', + ].filter((x) => content.includes(x)), + }; + + const passed = matches(result, expect); + console.log( + passed + ? { ['PASSED']: { cmd, url, out: JSON.stringify(result) } } + : { ['FAILED']: { cmd, url, result, expect } } + ); + + servor.kill(); + await browser.close(); +}; + +(async () => { + const base = { status: 200, gzip: true, cors: true, reload: false }; + + await test('node cli test')('http://localhost:8080')({ + ...base, + includes: ['SERVOR_TEST_INDEX'], + })(); + + await test('node cli test')('http://localhost:8080/nested')({ + ...base, + status: 301, + includes: ['SERVOR_TEST_INDEX'], + })(); + + await test('node cli test')('http://localhost:8080/assets/exists.png')({ + ...base, + gzip: false, + })(); + + await test('node cli test')('http://localhost:8080/assets/no-exists.png')({ + ...base, + status: 404, + gzip: false, + })(); + + await test('node cli test --reload')('http://localhost:8080')({ + ...base, + reload: true, + includes: ['SERVOR_TEST_INDEX'], + })(); + + await test('node cli test --routes')('http://localhost:8080')({ + ...base, + includes: ['SERVOR_TEST_INDEX'], + })(); + + await test('node cli test --routes')('http://localhost:8080/nested')({ + ...base, + includes: ['SERVOR_TEST_NESTED_INDEX'], + })(); + + await test('node cli test --module')('http://localhost:8080')({ + ...base, + includes: ['SERVOR_TEST_MODULE_INDEX'], + })(); + + await test('node cli test --secure')('https://localhost:8080')({ + ...base, + includes: ['SERVOR_TEST_INDEX'], + })(); + + await test('node cli test --secure --reload --routes --module')( + 'https://localhost:8080' + )({ + ...base, + reload: true, + includes: ['SERVOR_TEST_MODULE_INDEX'], + })(); + + await test('node cli test --secure --reload --routes --module')( + 'https://localhost:8080/nested' + )({ + ...base, + reload: true, + includes: ['SERVOR_TEST_NESTED_MODULE_INDEX'], + })(); +})(); diff --git a/test/index.html b/test/index.html index 095700b..19489db 100644 --- a/test/index.html +++ b/test/index.html @@ -2,7 +2,7 @@ - Web Application + SERVOR_TEST_INDEX diff --git a/test/index.js b/test/index.js index a636ff1..3ad0c42 100644 --- a/test/index.js +++ b/test/index.js @@ -1,3 +1,5 @@ +/* SERVOR_TEST_MODULE_INDEX */ + import { react, html, css } from 'https://unpkg.com/rplus'; const style = css` diff --git a/test/nested/index.html b/test/nested/index.html index 4f07149..4e5a63c 100644 --- a/test/nested/index.html +++ b/test/nested/index.html @@ -2,15 +2,11 @@ - Web Application + SERVOR_TEST_NESTED_INDEX - File that exists diff --git a/test/nested/index.js b/test/nested/index.js index 926f40a..1b73106 100644 --- a/test/nested/index.js +++ b/test/nested/index.js @@ -1,3 +1,5 @@ +/* SERVOR_TEST_NESTED_MODULE_INDEX */ + import { react, html, css } from 'https://unpkg.com/rplus'; const style = css` diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..12eb038 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,315 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@*": + version "13.13.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.5.tgz#96ec3b0afafd64a4ccea9107b75bf8489f0e5765" + integrity sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g== + +"@types/yauzl@^2.9.1": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" + integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== + dependencies: + "@types/node" "*" + +agent-base@5: + version "5.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" + integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + +bl@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" + integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + +buffer@^5.2.1, buffer@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +debug@4, debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +extract-zip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492" + integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +get-stream@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" + integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== + dependencies: + pump "^3.0.0" + +glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +https-proxy-agent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" + integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== + dependencies: + agent-base "5" + debug "4" + +ieee754@^1.1.4: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +mime@^2.0.3: + version "2.4.5" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.5.tgz#d8de2ecb92982dedbb6541c9b6841d7f218ea009" + integrity sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +progress@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +proxy-from-env@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +puppeteer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-3.0.4.tgz#f445aae0a6732c65bbb90e963dcd6fd8fde0d780" + integrity sha512-1QEb4tJXXbNId7WSHlcDkS3B4GklTIebKn8Y9D6B7tAdUjQncb+8QlTjbQsAgGX5dhRG32Qycuk5XKzJgLs0sg== + dependencies: + debug "^4.1.0" + extract-zip "^2.0.0" + https-proxy-agent "^4.0.0" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^3.0.2" + tar-fs "^2.0.0" + unbzip2-stream "^1.3.3" + ws "^7.2.3" + +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +tar-fs@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" + integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" + integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q== + dependencies: + bl "^4.0.1" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +unbzip2-stream@^1.3.3: + version "1.4.2" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.2.tgz#84eb9e783b186d8fb397515fbb656f312f1a7dbf" + integrity sha512-pZMVAofMrrHX6Ik39hCk470kulCbmZ2SWfQLPmTWqfJV/oUm0gn1CblvHdUu4+54Je6Jq34x8kY6XjTy6dMkOg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^7.2.3: + version "7.3.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd" + integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w== + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" From 9549185df3198b03f01524f3e36ef3d82b8a2cb3 Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Wed, 13 May 2020 11:20:57 +0100 Subject: [PATCH 04/14] Abstracts routing functions and wait for server before tests --- servor.js | 115 ++++++++++++++----------------- test.js | 97 +++++++++++++++++--------- test/assets/file with space.html | 0 3 files changed, 117 insertions(+), 95 deletions(-) create mode 100644 test/assets/file with space.html diff --git a/servor.js b/servor.js index 1e7f237..6771e5c 100644 --- a/servor.js +++ b/servor.js @@ -10,25 +10,20 @@ const zlib = require('zlib'); const cwd = process.cwd(); const watch = - process.platform === 'linux' - ? (path, cb) => { - if (fs.statSync(path).isDirectory()) { - fs.watch(path, cb); - fs.readdirSync(path).forEach((entry) => - watch(`${path}/${entry}`, cb) - ); + process.platform !== 'linux' + ? (x, cb) => fs.watch(x, { recursive: true }, cb) + : (x, cb) => { + if (fs.statSync(x).isDirectory()) { + fs.watch(x, cb); + fs.readdirSync(x).forEach((xx) => watch(`${x}/${xx}`, cb)); } - } - : (path, cb) => fs.watch(path, { recursive: true }, cb); + }; -const fport = (p = 0) => - new Promise((resolve, reject) => { +const freePort = (port = 0) => + new Promise((ok, x) => { const s = net.createServer(); - s.on('error', reject); - s.listen(p, () => { - const { port } = s.address(); - s.close(() => resolve(port)); - }); + s.on('error', x); + s.listen(port, () => (a = s.address()) && s.close(() => ok(a.port))); }); const ips = Object.values(os.networkInterfaces()) @@ -52,22 +47,22 @@ module.exports = async ({ inject = '', credentials, } = {}) => { - // Try start on specified port or find a free one + // Try start on specified port then fail or find a free port try { - port = await fport(port || process.env.PORT || 8080); + port = await freePort(port || process.env.PORT || 8080); } catch (e) { if (port || process.env.PORT) { console.log('[ERR] The port you have specified is already in use!'); process.exit(); } - port = await fport(); + port = await freePort(); } // Configure globals root = root.startsWith('/') ? root : path.join(cwd, root); - const clients = []; + const reloadClients = []; const protocol = credentials ? 'https' : 'http'; const server = credentials ? reload @@ -89,10 +84,11 @@ module.exports = async ({ // Server utility functions + const isRouteRequest = (pathname) => !~pathname.split('/').pop().indexOf('.'); const utf8 = (file) => Buffer.from(file, 'binary').toString('utf8'); const sendError = (res, status) => { - res.writeHead(status, { 'Access-Control-Allow-Origin': '*' }); + res.writeHead(status); res.write(`${status}`); res.end(); }; @@ -106,7 +102,6 @@ module.exports = async ({ } res.writeHead(status, { 'Content-Type': mimes[ext] || 'application/octet-stream', - 'Access-Control-Allow-Origin': '*', }); res.write(file, encoding); res.end(); @@ -117,68 +112,64 @@ module.exports = async ({ res.write('\n\n'); }; - const indexFileExists = (pathname) => - fs.existsSync(`${root}${pathname}/${fallback}`); - - const isRouteRequest = (pathname) => !~pathname.split('/').pop().indexOf('.'); - - const registerClient = (res) => { + const serveReload = (res) => { res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', - 'Access-Control-Allow-Origin': '*', }); sendMessage(res, 'connected', 'ready'); setInterval(sendMessage, 60000, res, 'ping', 'waiting'); - clients.push(res); + reloadClients.push(res); + }; + + const serveStaticFile = (res, pathname) => { + const uri = path.join(root, pathname); + let ext = uri.replace(/^.*[\.\/\\]/, '').toLowerCase(); + if (!fs.existsSync(uri)) return sendError(res, 404); + fs.readFile(uri, 'binary', (err, file) => + err ? sendError(res, 500) : sendFile(res, 200, file, ext) + ); + }; + + const serveRoute = (res, pathname) => { + const uri = routes + ? path.join(root, pathname, fallback) + : path.join(root, fallback); + if (!fs.existsSync(uri)) return sendError(res, 404); + fs.readFile(uri, 'binary', (err, file) => { + if (err) return sendError(res, 500); + const status = pathname === '/' || routes ? 200 : 301; + const base = path.join('/', pathname, '/'); + const doc = ``; + if (module) file = ``; + file = doc + file + inject + livereload; + sendFile(res, status, file, 'html'); + }); }; // Start the server on the desired port server((req, res) => { - const pathname = url.parse(req.url).pathname; - if (reload && pathname === '/livereload') registerClient(res); - else { - const isRoute = isRouteRequest(pathname); - const hasIndex = isRoute && routes && indexFileExists(pathname); - const status = !isRoute || hasIndex || pathname === '/' ? 200 : 301; - console.log(status, pathname, !isRoute || hasIndex || pathname === '/'); - - const resource = isRoute - ? hasIndex - ? `/${decodeURI(pathname)}/${fallback}` - : `/${fallback}` - : decodeURI(pathname); - const uri = path.join(root, resource); - let ext = uri.replace(/^.*[\.\/\\]/, '').toLowerCase(); - if (!fs.existsSync(uri)) return sendError(res, 404); - fs.readFile(uri, 'binary', (err, file) => { - if (err) return sendError(res, 500); - if (isRoute) { - const base = path.join('/', pathname, '/'); - const doc = ``; - if (module) file = ``; - file = doc + file + inject + livereload; - ext = 'html'; - } - sendFile(res, status, file, ext); - }); - } + const pathname = decodeURI(url.parse(req.url).pathname); + res.setHeader('Access-Control-Allow-Origin', '*'); + if (reload && pathname === '/livereload') return serveReload(res); + if (!isRouteRequest(pathname)) return serveStaticFile(res, pathname); + return serveRoute(res, pathname); }).listen(parseInt(port, 10)); - // Notify livereload clients on file change + // Notify livereload reloadClients on file change reload && watch(root, () => { - while (clients.length > 0) - sendMessage(clients.pop(), 'message', 'reload'); + while (reloadClients.length > 0) + sendMessage(reloadClients.pop(), 'message', 'reload'); }); // Close socket connections on sigint process.on('SIGINT', () => { - while (clients.length > 0) clients.pop().end(); + while (reloadClients.length > 0) reloadClients.pop().end(); process.exit(); }); diff --git a/test.js b/test.js index e7d1de2..2035623 100644 --- a/test.js +++ b/test.js @@ -9,16 +9,25 @@ const matches = (obj, source) => JSON.stringify(obj[key]) === JSON.stringify(source[key]) ); -const test = (cmd) => (url) => (expect) => async () => { +const modifyFile = (x) => + fs.readFile(x, 'utf-8', (_, data) => { + fs.writeFileSync(x, data, 'utf-8'); + }); + +const test = (cmd) => (url) => async (expect) => { // Make sure nothing is running on port 8080 cp.execSync( "lsof -n -i4TCP:8080 | grep LISTEN | awk '{ print $2 }' | xargs kill" ); // Run the command and wait for the server to start - const [c, ...a] = cmd.split(' '); + const [c, ...a] = ('node cli test ' + cmd).trim().split(' '); const servor = cp.spawn(c, a); - await new Promise((resolve) => servor.stdout.on('data', resolve)); + const { origin } = await new Promise((resolve) => + servor.stdout.once('data', (out) => { + resolve(new URL(out.toString().match(/Local:\t(.*)\n/)[1])); + }) + ); const browser = await puppeteer.launch({ ignoreHTTPSErrors: true, @@ -30,7 +39,7 @@ const test = (cmd) => (url) => (expect) => async () => { const page = await browser.newPage(); await page.setCacheEnabled(false); - const res = await page.goto(url); + const res = await page.goto(`${origin}${url}`); // Collect data from response and page const status = res.status(); @@ -40,9 +49,9 @@ const test = (cmd) => (url) => (expect) => async () => { // Change a file to trigger reload let reload = false; if (cmd.includes('--reload')) { - fs.readFile('test/index.html', 'utf-8', (_, data) => { - fs.writeFileSync('test/index.html', data, 'utf-8'); - }); + modifyFile('test/index.html'); + reload = await page.waitForNavigation({ timeout: 1000 }).catch(() => false); + modifyFile('test/assets/index.js'); reload = await page.waitForNavigation({ timeout: 1000 }).catch(() => false); } @@ -73,67 +82,89 @@ const test = (cmd) => (url) => (expect) => async () => { (async () => { const base = { status: 200, gzip: true, cors: true, reload: false }; - await test('node cli test')('http://localhost:8080')({ + await test('')('/')({ ...base, includes: ['SERVOR_TEST_INDEX'], - })(); + }); + + await test('')('/index.html')({ + ...base, + includes: ['SERVOR_TEST_INDEX'], + }); + + await test('')('/assets/file with space.html')({ + ...base, + }); + + await test('')('/assets/exists.png')({ + ...base, + gzip: false, + }); + + await test('')('/assets/no-exists.png')({ + ...base, + status: 404, + gzip: false, + }); - await test('node cli test')('http://localhost:8080/nested')({ + await test('')('/nested')({ ...base, status: 301, includes: ['SERVOR_TEST_INDEX'], - })(); + }); - await test('node cli test')('http://localhost:8080/assets/exists.png')({ + await test('')('/nested/assets/exists.png')({ ...base, gzip: false, - })(); + }); - await test('node cli test')('http://localhost:8080/assets/no-exists.png')({ + await test('')('/nested/assets/no-xists.png')({ ...base, status: 404, gzip: false, - })(); + }); - await test('node cli test --reload')('http://localhost:8080')({ + await test('--reload')('/')({ ...base, reload: true, includes: ['SERVOR_TEST_INDEX'], - })(); + }); - await test('node cli test --routes')('http://localhost:8080')({ + await test('--routes')('/')({ ...base, includes: ['SERVOR_TEST_INDEX'], - })(); + }); - await test('node cli test --routes')('http://localhost:8080/nested')({ + await test('--routes')('/nested')({ ...base, includes: ['SERVOR_TEST_NESTED_INDEX'], - })(); + }); - await test('node cli test --module')('http://localhost:8080')({ + await test('--routes')('/broken-nested')({ + ...base, + status: 404, + gzip: false, + }); + + await test('--module')('/')({ ...base, includes: ['SERVOR_TEST_MODULE_INDEX'], - })(); + }); - await test('node cli test --secure')('https://localhost:8080')({ + await test('--secure')('/')({ ...base, includes: ['SERVOR_TEST_INDEX'], - })(); + }); - await test('node cli test --secure --reload --routes --module')( - 'https://localhost:8080' - )({ + await test('--secure --reload --routes --module')('/')({ ...base, reload: true, includes: ['SERVOR_TEST_MODULE_INDEX'], - })(); + }); - await test('node cli test --secure --reload --routes --module')( - 'https://localhost:8080/nested' - )({ + await test('--secure --reload --routes --module')('/nested')({ ...base, reload: true, includes: ['SERVOR_TEST_NESTED_MODULE_INDEX'], - })(); + }); })(); diff --git a/test/assets/file with space.html b/test/assets/file with space.html new file mode 100644 index 0000000..e69de29 From af234ef8a50b16104f03dc6604bc326f07619473 Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Wed, 13 May 2020 13:19:06 +0100 Subject: [PATCH 05/14] Create config and rename test to example --- certify.sh | 6 +- ca.conf => config/ca.conf | 0 config/mimetypes.js | 906 ++++++++++++++++++ ssl.conf => config/ssl.conf | 0 {test => example}/assets/exists.png | Bin {test => example}/assets/file with space.html | 0 {test => example}/assets/index.css | 0 {test => example}/assets/index.js | 0 {test => example}/index.html | 0 {test => example}/index.js | 0 {test => example}/nested/assets/exists.png | Bin {test => example}/nested/assets/index.css | 0 {test => example}/nested/assets/index.js | 0 {test => example}/nested/index.html | 0 {test => example}/nested/index.js | 0 servor.js | 24 +- test.js | 6 +- types.json | 1 - yarn.lock | 315 ------ 19 files changed, 921 insertions(+), 337 deletions(-) rename ca.conf => config/ca.conf (100%) create mode 100644 config/mimetypes.js rename ssl.conf => config/ssl.conf (100%) rename {test => example}/assets/exists.png (100%) rename {test => example}/assets/file with space.html (100%) rename {test => example}/assets/index.css (100%) rename {test => example}/assets/index.js (100%) rename {test => example}/index.html (100%) rename {test => example}/index.js (100%) rename {test => example}/nested/assets/exists.png (100%) rename {test => example}/nested/assets/index.css (100%) rename {test => example}/nested/assets/index.js (100%) rename {test => example}/nested/index.html (100%) rename {test => example}/nested/index.js (100%) delete mode 100644 types.json delete mode 100644 yarn.lock diff --git a/certify.sh b/certify.sh index 955ce09..836942a 100755 --- a/certify.sh +++ b/certify.sh @@ -19,7 +19,7 @@ fi # Generate Certificate Authority openssl genrsa -out "tmp/${name}CA.key" 2048 &>/dev/null -openssl req -x509 -config ca.conf -new -nodes -key "tmp/${name}CA.key" -sha256 -days 1825 -out "${name}CA.pem" &>/dev/null +openssl req -x509 -config config/ca.conf -new -nodes -key "tmp/${name}CA.key" -sha256 -days 1825 -out "${name}CA.pem" &>/dev/null # This is the part that demands root privileges if [ "$EUID" -eq 0 ] ; then @@ -33,10 +33,10 @@ fi # Generate CA-signed Certificate openssl genrsa -out "${name}.key" 2048 &>/dev/null -openssl req -new -config ca.conf -key "${name}.key" -out "tmp/${name}.csr" &>/dev/null +openssl req -new -config config/ca.conf -key "${name}.key" -out "tmp/${name}.csr" &>/dev/null # Generate SSL Certificate -openssl x509 -req -in "tmp/${name}.csr" -CA "${name}CA.pem" -CAkey "tmp/${name}CA.key" -CAcreateserial -out "${name}.crt" -days 1825 -sha256 -extfile ssl.conf &>/dev/null +openssl x509 -req -in "tmp/${name}.csr" -CA "${name}CA.pem" -CAkey "tmp/${name}CA.key" -CAcreateserial -out "${name}.crt" -days 1825 -sha256 -extfile config/ssl.conf &>/dev/null # Cleanup files rm servorCA.pem servorCA.srl diff --git a/ca.conf b/config/ca.conf similarity index 100% rename from ca.conf rename to config/ca.conf diff --git a/config/mimetypes.js b/config/mimetypes.js new file mode 100644 index 0000000..0ebeed8 --- /dev/null +++ b/config/mimetypes.js @@ -0,0 +1,906 @@ +const types = { + 'application/andrew-inset': ['ez'], + 'application/applixware': ['aw'], + 'application/atom+xml': ['atom'], + 'application/atomcat+xml': ['atomcat'], + 'application/atomsvc+xml': ['atomsvc'], + 'application/bdoc': ['bdoc'], + 'application/ccxml+xml': ['ccxml'], + 'application/cdmi-capability': ['cdmia'], + 'application/cdmi-container': ['cdmic'], + 'application/cdmi-domain': ['cdmid'], + 'application/cdmi-object': ['cdmio'], + 'application/cdmi-queue': ['cdmiq'], + 'application/cu-seeme': ['cu'], + 'application/dash+xml': ['mpd'], + 'application/davmount+xml': ['davmount'], + 'application/docbook+xml': ['dbk'], + 'application/dssc+der': ['dssc'], + 'application/dssc+xml': ['xdssc'], + 'application/ecmascript': ['ecma'], + 'application/emma+xml': ['emma'], + 'application/epub+zip': ['epub'], + 'application/exi': ['exi'], + 'application/font-tdpfr': ['pfr'], + 'application/font-woff': [], + 'application/font-woff2': [], + 'application/geo+json': ['geojson'], + 'application/gml+xml': ['gml'], + 'application/gpx+xml': ['gpx'], + 'application/gxf': ['gxf'], + 'application/gzip': ['gz'], + 'application/hyperstudio': ['stk'], + 'application/inkml+xml': ['ink', 'inkml'], + 'application/ipfix': ['ipfix'], + 'application/java-archive': ['jar', 'war', 'ear'], + 'application/java-serialized-object': ['ser'], + 'application/java-vm': ['class'], + 'application/javascript': ['js', 'mjs'], + 'application/json': ['json', 'map'], + 'application/json5': ['json5'], + 'application/jsonml+json': ['jsonml'], + 'application/ld+json': ['jsonld'], + 'application/lost+xml': ['lostxml'], + 'application/mac-binhex40': ['hqx'], + 'application/mac-compactpro': ['cpt'], + 'application/mads+xml': ['mads'], + 'application/manifest+json': ['webmanifest'], + 'application/marc': ['mrc'], + 'application/marcxml+xml': ['mrcx'], + 'application/mathematica': ['ma', 'nb', 'mb'], + 'application/mathml+xml': ['mathml'], + 'application/mbox': ['mbox'], + 'application/mediaservercontrol+xml': ['mscml'], + 'application/metalink+xml': ['metalink'], + 'application/metalink4+xml': ['meta4'], + 'application/mets+xml': ['mets'], + 'application/mods+xml': ['mods'], + 'application/mp21': ['m21', 'mp21'], + 'application/mp4': ['mp4s', 'm4p'], + 'application/msword': ['doc', 'dot'], + 'application/mxf': ['mxf'], + 'application/octet-stream': [ + 'bin', + 'dms', + 'lrf', + 'mar', + 'so', + 'dist', + 'distz', + 'pkg', + 'bpk', + 'dump', + 'elc', + 'deploy', + 'exe', + 'dll', + 'deb', + 'dmg', + 'iso', + 'img', + 'msi', + 'msp', + 'msm', + 'buffer', + ], + 'application/oda': ['oda'], + 'application/oebps-package+xml': ['opf'], + 'application/ogg': ['ogx'], + 'application/omdoc+xml': ['omdoc'], + 'application/onenote': ['onetoc', 'onetoc2', 'onetmp', 'onepkg'], + 'application/oxps': ['oxps'], + 'application/patch-ops-error+xml': ['xer'], + 'application/pdf': ['pdf'], + 'application/pgp-encrypted': ['pgp'], + 'application/pgp-signature': ['asc', 'sig'], + 'application/pics-rules': ['prf'], + 'application/pkcs10': ['p10'], + 'application/pkcs7-mime': ['p7m', 'p7c'], + 'application/pkcs7-signature': ['p7s'], + 'application/pkcs8': ['p8'], + 'application/pkix-attr-cert': ['ac'], + 'application/pkix-cert': ['cer'], + 'application/pkix-crl': ['crl'], + 'application/pkix-pkipath': ['pkipath'], + 'application/pkixcmp': ['pki'], + 'application/pls+xml': ['pls'], + 'application/postscript': ['ai', 'eps', 'ps'], + 'application/prs.cww': ['cww'], + 'application/pskc+xml': ['pskcxml'], + 'application/raml+yaml': ['raml'], + 'application/rdf+xml': ['rdf'], + 'application/reginfo+xml': ['rif'], + 'application/relax-ng-compact-syntax': ['rnc'], + 'application/resource-lists+xml': ['rl'], + 'application/resource-lists-diff+xml': ['rld'], + 'application/rls-services+xml': ['rs'], + 'application/rpki-ghostbusters': ['gbr'], + 'application/rpki-manifest': ['mft'], + 'application/rpki-roa': ['roa'], + 'application/rsd+xml': ['rsd'], + 'application/rss+xml': ['rss'], + 'application/rtf': ['rtf'], + 'application/sbml+xml': ['sbml'], + 'application/scvp-cv-request': ['scq'], + 'application/scvp-cv-response': ['scs'], + 'application/scvp-vp-request': ['spq'], + 'application/scvp-vp-response': ['spp'], + 'application/sdp': ['sdp'], + 'application/set-payment-initiation': ['setpay'], + 'application/set-registration-initiation': ['setreg'], + 'application/shf+xml': ['shf'], + 'application/smil+xml': ['smi', 'smil'], + 'application/sparql-query': ['rq'], + 'application/sparql-results+xml': ['srx'], + 'application/srgs': ['gram'], + 'application/srgs+xml': ['grxml'], + 'application/sru+xml': ['sru'], + 'application/ssdl+xml': ['ssdl'], + 'application/ssml+xml': ['ssml'], + 'application/tei+xml': ['tei', 'teicorpus'], + 'application/thraud+xml': ['tfi'], + 'application/timestamped-data': ['tsd'], + 'application/vnd.3gpp.pic-bw-large': ['plb'], + 'application/vnd.3gpp.pic-bw-small': ['psb'], + 'application/vnd.3gpp.pic-bw-var': ['pvb'], + 'application/vnd.3gpp2.tcap': ['tcap'], + 'application/vnd.3m.post-it-notes': ['pwn'], + 'application/vnd.accpac.simply.aso': ['aso'], + 'application/vnd.accpac.simply.imp': ['imp'], + 'application/vnd.acucobol': ['acu'], + 'application/vnd.acucorp': ['atc', 'acutc'], + 'application/vnd.adobe.air-application-installer-package+zip': ['air'], + 'application/vnd.adobe.formscentral.fcdt': ['fcdt'], + 'application/vnd.adobe.fxp': ['fxp', 'fxpl'], + 'application/vnd.adobe.xdp+xml': ['xdp'], + 'application/vnd.adobe.xfdf': ['xfdf'], + 'application/vnd.ahead.space': ['ahead'], + 'application/vnd.airzip.filesecure.azf': ['azf'], + 'application/vnd.airzip.filesecure.azs': ['azs'], + 'application/vnd.amazon.ebook': ['azw'], + 'application/vnd.americandynamics.acc': ['acc'], + 'application/vnd.amiga.ami': ['ami'], + 'application/vnd.android.package-archive': ['apk'], + 'application/vnd.anser-web-certificate-issue-initiation': ['cii'], + 'application/vnd.anser-web-funds-transfer-initiation': ['fti'], + 'application/vnd.antix.game-component': ['atx'], + 'application/vnd.apple.installer+xml': ['mpkg'], + 'application/vnd.apple.mpegurl': ['m3u8'], + 'application/vnd.apple.pkpass': ['pkpass'], + 'application/vnd.aristanetworks.swi': ['swi'], + 'application/vnd.astraea-software.iota': ['iota'], + 'application/vnd.audiograph': ['aep'], + 'application/vnd.blueice.multipass': ['mpm'], + 'application/vnd.bmi': ['bmi'], + 'application/vnd.businessobjects': ['rep'], + 'application/vnd.chemdraw+xml': ['cdxml'], + 'application/vnd.chipnuts.karaoke-mmd': ['mmd'], + 'application/vnd.cinderella': ['cdy'], + 'application/vnd.claymore': ['cla'], + 'application/vnd.cloanto.rp9': ['rp9'], + 'application/vnd.clonk.c4group': ['c4g', 'c4d', 'c4f', 'c4p', 'c4u'], + 'application/vnd.cluetrust.cartomobile-config': ['c11amc'], + 'application/vnd.cluetrust.cartomobile-config-pkg': ['c11amz'], + 'application/vnd.commonspace': ['csp'], + 'application/vnd.contact.cmsg': ['cdbcmsg'], + 'application/vnd.cosmocaller': ['cmc'], + 'application/vnd.crick.clicker': ['clkx'], + 'application/vnd.crick.clicker.keyboard': ['clkk'], + 'application/vnd.crick.clicker.palette': ['clkp'], + 'application/vnd.crick.clicker.template': ['clkt'], + 'application/vnd.crick.clicker.wordbank': ['clkw'], + 'application/vnd.criticaltools.wbs+xml': ['wbs'], + 'application/vnd.ctc-posml': ['pml'], + 'application/vnd.cups-ppd': ['ppd'], + 'application/vnd.curl.car': ['car'], + 'application/vnd.curl.pcurl': ['pcurl'], + 'application/vnd.dart': ['dart'], + 'application/vnd.data-vision.rdz': ['rdz'], + 'application/vnd.dece.data': ['uvf', 'uvvf', 'uvd', 'uvvd'], + 'application/vnd.dece.ttml+xml': ['uvt', 'uvvt'], + 'application/vnd.dece.unspecified': ['uvx', 'uvvx'], + 'application/vnd.dece.zip': ['uvz', 'uvvz'], + 'application/vnd.denovo.fcselayout-link': ['fe_launch'], + 'application/vnd.dna': ['dna'], + 'application/vnd.dolby.mlp': ['mlp'], + 'application/vnd.dpgraph': ['dpg'], + 'application/vnd.dreamfactory': ['dfac'], + 'application/vnd.ds-keypoint': ['kpxx'], + 'application/vnd.dvb.ait': ['ait'], + 'application/vnd.dvb.service': ['svc'], + 'application/vnd.dynageo': ['geo'], + 'application/vnd.ecowin.chart': ['mag'], + 'application/vnd.enliven': ['nml'], + 'application/vnd.epson.esf': ['esf'], + 'application/vnd.epson.msf': ['msf'], + 'application/vnd.epson.quickanime': ['qam'], + 'application/vnd.epson.salt': ['slt'], + 'application/vnd.epson.ssf': ['ssf'], + 'application/vnd.eszigno3+xml': ['es3', 'et3'], + 'application/vnd.ezpix-album': ['ez2'], + 'application/vnd.ezpix-package': ['ez3'], + 'application/vnd.fdf': ['fdf'], + 'application/vnd.fdsn.mseed': ['mseed'], + 'application/vnd.fdsn.seed': ['seed', 'dataless'], + 'application/vnd.flographit': ['gph'], + 'application/vnd.fluxtime.clip': ['ftc'], + 'application/vnd.framemaker': ['fm', 'frame', 'maker', 'book'], + 'application/vnd.frogans.fnc': ['fnc'], + 'application/vnd.frogans.ltf': ['ltf'], + 'application/vnd.fsc.weblaunch': ['fsc'], + 'application/vnd.fujitsu.oasys': ['oas'], + 'application/vnd.fujitsu.oasys2': ['oa2'], + 'application/vnd.fujitsu.oasys3': ['oa3'], + 'application/vnd.fujitsu.oasysgp': ['fg5'], + 'application/vnd.fujitsu.oasysprs': ['bh2'], + 'application/vnd.fujixerox.ddd': ['ddd'], + 'application/vnd.fujixerox.docuworks': ['xdw'], + 'application/vnd.fujixerox.docuworks.binder': ['xbd'], + 'application/vnd.fuzzysheet': ['fzs'], + 'application/vnd.genomatix.tuxedo': ['txd'], + 'application/vnd.geogebra.file': ['ggb'], + 'application/vnd.geogebra.tool': ['ggt'], + 'application/vnd.geometry-explorer': ['gex', 'gre'], + 'application/vnd.geonext': ['gxt'], + 'application/vnd.geoplan': ['g2w'], + 'application/vnd.geospace': ['g3w'], + 'application/vnd.gmx': ['gmx'], + 'application/vnd.google-apps.document': ['gdoc'], + 'application/vnd.google-apps.presentation': ['gslides'], + 'application/vnd.google-apps.spreadsheet': ['gsheet'], + 'application/vnd.google-earth.kml+xml': ['kml'], + 'application/vnd.google-earth.kmz': ['kmz'], + 'application/vnd.grafeq': ['gqf', 'gqs'], + 'application/vnd.groove-account': ['gac'], + 'application/vnd.groove-help': ['ghf'], + 'application/vnd.groove-identity-message': ['gim'], + 'application/vnd.groove-injector': ['grv'], + 'application/vnd.groove-tool-message': ['gtm'], + 'application/vnd.groove-tool-template': ['tpl'], + 'application/vnd.groove-vcard': ['vcg'], + 'application/vnd.hal+xml': ['hal'], + 'application/vnd.handheld-entertainment+xml': ['zmm'], + 'application/vnd.hbci': ['hbci'], + 'application/vnd.hhe.lesson-player': ['les'], + 'application/vnd.hp-hpgl': ['hpgl'], + 'application/vnd.hp-hpid': ['hpid'], + 'application/vnd.hp-hps': ['hps'], + 'application/vnd.hp-jlyt': ['jlt'], + 'application/vnd.hp-pcl': ['pcl'], + 'application/vnd.hp-pclxl': ['pclxl'], + 'application/vnd.hydrostatix.sof-data': ['sfd-hdstx'], + 'application/vnd.ibm.minipay': ['mpy'], + 'application/vnd.ibm.modcap': ['afp', 'listafp', 'list3820'], + 'application/vnd.ibm.rights-management': ['irm'], + 'application/vnd.ibm.secure-container': ['sc'], + 'application/vnd.iccprofile': ['icc', 'icm'], + 'application/vnd.igloader': ['igl'], + 'application/vnd.immervision-ivp': ['ivp'], + 'application/vnd.immervision-ivu': ['ivu'], + 'application/vnd.insors.igm': ['igm'], + 'application/vnd.intercon.formnet': ['xpw', 'xpx'], + 'application/vnd.intergeo': ['i2g'], + 'application/vnd.intu.qbo': ['qbo'], + 'application/vnd.intu.qfx': ['qfx'], + 'application/vnd.ipunplugged.rcprofile': ['rcprofile'], + 'application/vnd.irepository.package+xml': ['irp'], + 'application/vnd.is-xpr': ['xpr'], + 'application/vnd.isac.fcs': ['fcs'], + 'application/vnd.jam': ['jam'], + 'application/vnd.jcp.javame.midlet-rms': ['rms'], + 'application/vnd.jisp': ['jisp'], + 'application/vnd.joost.joda-archive': ['joda'], + 'application/vnd.kahootz': ['ktz', 'ktr'], + 'application/vnd.kde.karbon': ['karbon'], + 'application/vnd.kde.kchart': ['chrt'], + 'application/vnd.kde.kformula': ['kfo'], + 'application/vnd.kde.kivio': ['flw'], + 'application/vnd.kde.kontour': ['kon'], + 'application/vnd.kde.kpresenter': ['kpr', 'kpt'], + 'application/vnd.kde.kspread': ['ksp'], + 'application/vnd.kde.kword': ['kwd', 'kwt'], + 'application/vnd.kenameaapp': ['htke'], + 'application/vnd.kidspiration': ['kia'], + 'application/vnd.kinar': ['kne', 'knp'], + 'application/vnd.koan': ['skp', 'skd', 'skt', 'skm'], + 'application/vnd.kodak-descriptor': ['sse'], + 'application/vnd.las.las+xml': ['lasxml'], + 'application/vnd.llamagraphics.life-balance.desktop': ['lbd'], + 'application/vnd.llamagraphics.life-balance.exchange+xml': ['lbe'], + 'application/vnd.lotus-1-2-3': ['123'], + 'application/vnd.lotus-approach': ['apr'], + 'application/vnd.lotus-freelance': ['pre'], + 'application/vnd.lotus-notes': ['nsf'], + 'application/vnd.lotus-organizer': ['org'], + 'application/vnd.lotus-screencam': ['scm'], + 'application/vnd.lotus-wordpro': ['lwp'], + 'application/vnd.macports.portpkg': ['portpkg'], + 'application/vnd.mcd': ['mcd'], + 'application/vnd.medcalcdata': ['mc1'], + 'application/vnd.mediastation.cdkey': ['cdkey'], + 'application/vnd.mfer': ['mwf'], + 'application/vnd.mfmp': ['mfm'], + 'application/vnd.micrografx.flo': ['flo'], + 'application/vnd.micrografx.igx': ['igx'], + 'application/vnd.mif': ['mif'], + 'application/vnd.mobius.daf': ['daf'], + 'application/vnd.mobius.dis': ['dis'], + 'application/vnd.mobius.mbk': ['mbk'], + 'application/vnd.mobius.mqy': ['mqy'], + 'application/vnd.mobius.msl': ['msl'], + 'application/vnd.mobius.plc': ['plc'], + 'application/vnd.mobius.txf': ['txf'], + 'application/vnd.mophun.application': ['mpn'], + 'application/vnd.mophun.certificate': ['mpc'], + 'application/vnd.mozilla.xul+xml': ['xul'], + 'application/vnd.ms-artgalry': ['cil'], + 'application/vnd.ms-cab-compressed': ['cab'], + 'application/vnd.ms-excel': ['xls', 'xlm', 'xla', 'xlc', 'xlt', 'xlw'], + 'application/vnd.ms-excel.addin.macroenabled.12': ['xlam'], + 'application/vnd.ms-excel.sheet.binary.macroenabled.12': ['xlsb'], + 'application/vnd.ms-excel.sheet.macroenabled.12': ['xlsm'], + 'application/vnd.ms-excel.template.macroenabled.12': ['xltm'], + 'application/vnd.ms-fontobject': ['eot'], + 'application/vnd.ms-htmlhelp': ['chm'], + 'application/vnd.ms-ims': ['ims'], + 'application/vnd.ms-lrm': ['lrm'], + 'application/vnd.ms-officetheme': ['thmx'], + 'application/vnd.ms-outlook': ['msg'], + 'application/vnd.ms-pki.seccat': ['cat'], + 'application/vnd.ms-pki.stl': ['stl'], + 'application/vnd.ms-powerpoint': ['ppt', 'pps', 'pot'], + 'application/vnd.ms-powerpoint.addin.macroenabled.12': ['ppam'], + 'application/vnd.ms-powerpoint.presentation.macroenabled.12': ['pptm'], + 'application/vnd.ms-powerpoint.slide.macroenabled.12': ['sldm'], + 'application/vnd.ms-powerpoint.slideshow.macroenabled.12': ['ppsm'], + 'application/vnd.ms-powerpoint.template.macroenabled.12': ['potm'], + 'application/vnd.ms-project': ['mpp', 'mpt'], + 'application/vnd.ms-word.document.macroenabled.12': ['docm'], + 'application/vnd.ms-word.template.macroenabled.12': ['dotm'], + 'application/vnd.ms-works': ['wps', 'wks', 'wcm', 'wdb'], + 'application/vnd.ms-wpl': ['wpl'], + 'application/vnd.ms-xpsdocument': ['xps'], + 'application/vnd.mseq': ['mseq'], + 'application/vnd.musician': ['mus'], + 'application/vnd.muvee.style': ['msty'], + 'application/vnd.mynfc': ['taglet'], + 'application/vnd.neurolanguage.nlu': ['nlu'], + 'application/vnd.nitf': ['ntf', 'nitf'], + 'application/vnd.noblenet-directory': ['nnd'], + 'application/vnd.noblenet-sealer': ['nns'], + 'application/vnd.noblenet-web': ['nnw'], + 'application/vnd.nokia.n-gage.data': ['ngdat'], + 'application/vnd.nokia.n-gage.symbian.install': ['n-gage'], + 'application/vnd.nokia.radio-preset': ['rpst'], + 'application/vnd.nokia.radio-presets': ['rpss'], + 'application/vnd.novadigm.edm': ['edm'], + 'application/vnd.novadigm.edx': ['edx'], + 'application/vnd.novadigm.ext': ['ext'], + 'application/vnd.oasis.opendocument.chart': ['odc'], + 'application/vnd.oasis.opendocument.chart-template': ['otc'], + 'application/vnd.oasis.opendocument.database': ['odb'], + 'application/vnd.oasis.opendocument.formula': ['odf'], + 'application/vnd.oasis.opendocument.formula-template': ['odft'], + 'application/vnd.oasis.opendocument.graphics': ['odg'], + 'application/vnd.oasis.opendocument.graphics-template': ['otg'], + 'application/vnd.oasis.opendocument.image': ['odi'], + 'application/vnd.oasis.opendocument.image-template': ['oti'], + 'application/vnd.oasis.opendocument.presentation': ['odp'], + 'application/vnd.oasis.opendocument.presentation-template': ['otp'], + 'application/vnd.oasis.opendocument.spreadsheet': ['ods'], + 'application/vnd.oasis.opendocument.spreadsheet-template': ['ots'], + 'application/vnd.oasis.opendocument.text': ['odt'], + 'application/vnd.oasis.opendocument.text-master': ['odm'], + 'application/vnd.oasis.opendocument.text-template': ['ott'], + 'application/vnd.oasis.opendocument.text-web': ['oth'], + 'application/vnd.olpc-sugar': ['xo'], + 'application/vnd.oma.dd2+xml': ['dd2'], + 'application/vnd.openofficeorg.extension': ['oxt'], + 'application/vnd.openxmlformats-officedocument.presentationml.presentation': [ + 'pptx', + ], + 'application/vnd.openxmlformats-officedocument.presentationml.slide': [ + 'sldx', + ], + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow': [ + 'ppsx', + ], + 'application/vnd.openxmlformats-officedocument.presentationml.template': [ + 'potx', + ], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['xlsx'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template': [ + 'xltx', + ], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': [ + 'docx', + ], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template': [ + 'dotx', + ], + 'application/vnd.osgeo.mapguide.package': ['mgp'], + 'application/vnd.osgi.dp': ['dp'], + 'application/vnd.osgi.subsystem': ['esa'], + 'application/vnd.palm': ['pdb', 'pqa', 'oprc'], + 'application/vnd.pawaafile': ['paw'], + 'application/vnd.pg.format': ['str'], + 'application/vnd.pg.osasli': ['ei6'], + 'application/vnd.picsel': ['efif'], + 'application/vnd.pmi.widget': ['wg'], + 'application/vnd.pocketlearn': ['plf'], + 'application/vnd.powerbuilder6': ['pbd'], + 'application/vnd.previewsystems.box': ['box'], + 'application/vnd.proteus.magazine': ['mgz'], + 'application/vnd.publishare-delta-tree': ['qps'], + 'application/vnd.pvi.ptid1': ['ptid'], + 'application/vnd.quark.quarkxpress': [ + 'qxd', + 'qxt', + 'qwd', + 'qwt', + 'qxl', + 'qxb', + ], + 'application/vnd.realvnc.bed': ['bed'], + 'application/vnd.recordare.musicxml': ['mxl'], + 'application/vnd.recordare.musicxml+xml': ['musicxml'], + 'application/vnd.rig.cryptonote': ['cryptonote'], + 'application/vnd.rim.cod': ['cod'], + 'application/vnd.rn-realmedia': ['rm'], + 'application/vnd.rn-realmedia-vbr': ['rmvb'], + 'application/vnd.route66.link66+xml': ['link66'], + 'application/vnd.sailingtracker.track': ['st'], + 'application/vnd.seemail': ['see'], + 'application/vnd.sema': ['sema'], + 'application/vnd.semd': ['semd'], + 'application/vnd.semf': ['semf'], + 'application/vnd.shana.informed.formdata': ['ifm'], + 'application/vnd.shana.informed.formtemplate': ['itp'], + 'application/vnd.shana.informed.interchange': ['iif'], + 'application/vnd.shana.informed.package': ['ipk'], + 'application/vnd.simtech-mindmapper': ['twd', 'twds'], + 'application/vnd.smaf': ['mmf'], + 'application/vnd.smart.teacher': ['teacher'], + 'application/vnd.solent.sdkm+xml': ['sdkm', 'sdkd'], + 'application/vnd.spotfire.dxp': ['dxp'], + 'application/vnd.spotfire.sfs': ['sfs'], + 'application/vnd.stardivision.calc': ['sdc'], + 'application/vnd.stardivision.draw': ['sda'], + 'application/vnd.stardivision.impress': ['sdd'], + 'application/vnd.stardivision.math': ['smf'], + 'application/vnd.stardivision.writer': ['sdw', 'vor'], + 'application/vnd.stardivision.writer-global': ['sgl'], + 'application/vnd.stepmania.package': ['smzip'], + 'application/vnd.stepmania.stepchart': ['sm'], + 'application/vnd.sun.wadl+xml': ['wadl'], + 'application/vnd.sun.xml.calc': ['sxc'], + 'application/vnd.sun.xml.calc.template': ['stc'], + 'application/vnd.sun.xml.draw': ['sxd'], + 'application/vnd.sun.xml.draw.template': ['std'], + 'application/vnd.sun.xml.impress': ['sxi'], + 'application/vnd.sun.xml.impress.template': ['sti'], + 'application/vnd.sun.xml.math': ['sxm'], + 'application/vnd.sun.xml.writer': ['sxw'], + 'application/vnd.sun.xml.writer.global': ['sxg'], + 'application/vnd.sun.xml.writer.template': ['stw'], + 'application/vnd.sus-calendar': ['sus', 'susp'], + 'application/vnd.svd': ['svd'], + 'application/vnd.symbian.install': ['sis', 'sisx'], + 'application/vnd.syncml+xml': ['xsm'], + 'application/vnd.syncml.dm+wbxml': ['bdm'], + 'application/vnd.syncml.dm+xml': ['xdm'], + 'application/vnd.tao.intent-module-archive': ['tao'], + 'application/vnd.tcpdump.pcap': ['pcap', 'cap', 'dmp'], + 'application/vnd.tmobile-livetv': ['tmo'], + 'application/vnd.trid.tpt': ['tpt'], + 'application/vnd.triscape.mxs': ['mxs'], + 'application/vnd.trueapp': ['tra'], + 'application/vnd.ufdl': ['ufd', 'ufdl'], + 'application/vnd.uiq.theme': ['utz'], + 'application/vnd.umajin': ['umj'], + 'application/vnd.unity': ['unityweb'], + 'application/vnd.uoml+xml': ['uoml'], + 'application/vnd.vcx': ['vcx'], + 'application/vnd.visio': ['vsd', 'vst', 'vss', 'vsw'], + 'application/vnd.visionary': ['vis'], + 'application/vnd.vsf': ['vsf'], + 'application/vnd.wap.wbxml': ['wbxml'], + 'application/vnd.wap.wmlc': ['wmlc'], + 'application/vnd.wap.wmlscriptc': ['wmlsc'], + 'application/vnd.webturbo': ['wtb'], + 'application/vnd.wolfram.player': ['nbp'], + 'application/vnd.wordperfect': ['wpd'], + 'application/vnd.wqd': ['wqd'], + 'application/vnd.wt.stf': ['stf'], + 'application/vnd.xara': ['xar'], + 'application/vnd.xfdl': ['xfdl'], + 'application/vnd.yamaha.hv-dic': ['hvd'], + 'application/vnd.yamaha.hv-script': ['hvs'], + 'application/vnd.yamaha.hv-voice': ['hvp'], + 'application/vnd.yamaha.openscoreformat': ['osf'], + 'application/vnd.yamaha.openscoreformat.osfpvg+xml': ['osfpvg'], + 'application/vnd.yamaha.smaf-audio': ['saf'], + 'application/vnd.yamaha.smaf-phrase': ['spf'], + 'application/vnd.yellowriver-custom-menu': ['cmp'], + 'application/vnd.zul': ['zir', 'zirz'], + 'application/vnd.zzazz.deck+xml': ['zaz'], + 'application/voicexml+xml': ['vxml'], + 'application/wasm': ['wasm'], + 'application/widget': ['wgt'], + 'application/winhlp': ['hlp'], + 'application/wsdl+xml': ['wsdl'], + 'application/wspolicy+xml': ['wspolicy'], + 'application/x-7z-compressed': ['7z'], + 'application/x-abiword': ['abw'], + 'application/x-ace-compressed': ['ace'], + 'application/x-apple-diskimage': [], + 'application/x-arj': ['arj'], + 'application/x-authorware-bin': ['aab', 'x32', 'u32', 'vox'], + 'application/x-authorware-map': ['aam'], + 'application/x-authorware-seg': ['aas'], + 'application/x-bcpio': ['bcpio'], + 'application/x-bdoc': [], + 'application/x-bittorrent': ['torrent'], + 'application/x-blorb': ['blb', 'blorb'], + 'application/x-bzip': ['bz'], + 'application/x-bzip2': ['bz2', 'boz'], + 'application/x-cbr': ['cbr', 'cba', 'cbt', 'cbz', 'cb7'], + 'application/x-cdlink': ['vcd'], + 'application/x-cfs-compressed': ['cfs'], + 'application/x-chat': ['chat'], + 'application/x-chess-pgn': ['pgn'], + 'application/x-chrome-extension': ['crx'], + 'application/x-cocoa': ['cco'], + 'application/x-conference': ['nsc'], + 'application/x-cpio': ['cpio'], + 'application/x-csh': ['csh'], + 'application/x-debian-package': ['udeb'], + 'application/x-dgc-compressed': ['dgc'], + 'application/x-director': [ + 'dir', + 'dcr', + 'dxr', + 'cst', + 'cct', + 'cxt', + 'w3d', + 'fgd', + 'swa', + ], + 'application/x-doom': ['wad'], + 'application/x-dtbncx+xml': ['ncx'], + 'application/x-dtbook+xml': ['dtb'], + 'application/x-dtbresource+xml': ['res'], + 'application/x-dvi': ['dvi'], + 'application/x-envoy': ['evy'], + 'application/x-eva': ['eva'], + 'application/x-font-bdf': ['bdf'], + 'application/x-font-ghostscript': ['gsf'], + 'application/x-font-linux-psf': ['psf'], + 'application/x-font-pcf': ['pcf'], + 'application/x-font-snf': ['snf'], + 'application/x-font-type1': ['pfa', 'pfb', 'pfm', 'afm'], + 'application/x-freearc': ['arc'], + 'application/x-futuresplash': ['spl'], + 'application/x-gca-compressed': ['gca'], + 'application/x-glulx': ['ulx'], + 'application/x-gnumeric': ['gnumeric'], + 'application/x-gramps-xml': ['gramps'], + 'application/x-gtar': ['gtar'], + 'application/x-hdf': ['hdf'], + 'application/x-httpd-php': ['php'], + 'application/x-install-instructions': ['install'], + 'application/x-iso9660-image': [], + 'application/x-java-archive-diff': ['jardiff'], + 'application/x-java-jnlp-file': ['jnlp'], + 'application/x-latex': ['latex'], + 'application/x-lua-bytecode': ['luac'], + 'application/x-lzh-compressed': ['lzh', 'lha'], + 'application/x-makeself': ['run'], + 'application/x-mie': ['mie'], + 'application/x-mobipocket-ebook': ['prc', 'mobi'], + 'application/x-ms-application': ['application'], + 'application/x-ms-shortcut': ['lnk'], + 'application/x-ms-wmd': ['wmd'], + 'application/x-ms-wmz': ['wmz'], + 'application/x-ms-xbap': ['xbap'], + 'application/x-msaccess': ['mdb'], + 'application/x-msbinder': ['obd'], + 'application/x-mscardfile': ['crd'], + 'application/x-msclip': ['clp'], + 'application/x-msdos-program': [], + 'application/x-msdownload': ['com', 'bat'], + 'application/x-msmediaview': ['mvb', 'm13', 'm14'], + 'application/x-msmetafile': ['wmf', 'emf', 'emz'], + 'application/x-msmoney': ['mny'], + 'application/x-mspublisher': ['pub'], + 'application/x-msschedule': ['scd'], + 'application/x-msterminal': ['trm'], + 'application/x-mswrite': ['wri'], + 'application/x-netcdf': ['nc', 'cdf'], + 'application/x-ns-proxy-autoconfig': ['pac'], + 'application/x-nzb': ['nzb'], + 'application/x-perl': ['pl', 'pm'], + 'application/x-pilot': [], + 'application/x-pkcs12': ['p12', 'pfx'], + 'application/x-pkcs7-certificates': ['p7b', 'spc'], + 'application/x-pkcs7-certreqresp': ['p7r'], + 'application/x-rar-compressed': ['rar'], + 'application/x-redhat-package-manager': ['rpm'], + 'application/x-research-info-systems': ['ris'], + 'application/x-sea': ['sea'], + 'application/x-sh': ['sh'], + 'application/x-shar': ['shar'], + 'application/x-shockwave-flash': ['swf'], + 'application/x-silverlight-app': ['xap'], + 'application/x-sql': ['sql'], + 'application/x-stuffit': ['sit'], + 'application/x-stuffitx': ['sitx'], + 'application/x-subrip': ['srt'], + 'application/x-sv4cpio': ['sv4cpio'], + 'application/x-sv4crc': ['sv4crc'], + 'application/x-t3vm-image': ['t3'], + 'application/x-tads': ['gam'], + 'application/x-tar': ['tar'], + 'application/x-tcl': ['tcl', 'tk'], + 'application/x-tex': ['tex'], + 'application/x-tex-tfm': ['tfm'], + 'application/x-texinfo': ['texinfo', 'texi'], + 'application/x-tgif': ['obj'], + 'application/x-ustar': ['ustar'], + 'application/x-virtualbox-hdd': ['hdd'], + 'application/x-virtualbox-ova': ['ova'], + 'application/x-virtualbox-ovf': ['ovf'], + 'application/x-virtualbox-vbox': ['vbox'], + 'application/x-virtualbox-vbox-extpack': ['vbox-extpack'], + 'application/x-virtualbox-vdi': ['vdi'], + 'application/x-virtualbox-vhd': ['vhd'], + 'application/x-virtualbox-vmdk': ['vmdk'], + 'application/x-wais-source': ['src'], + 'application/x-web-app-manifest+json': ['webapp'], + 'application/x-x509-ca-cert': ['der', 'crt', 'pem'], + 'application/x-xfig': ['fig'], + 'application/x-xliff+xml': ['xlf'], + 'application/x-xpinstall': ['xpi'], + 'application/x-xz': ['xz'], + 'application/x-zmachine': ['z1', 'z2', 'z3', 'z4', 'z5', 'z6', 'z7', 'z8'], + 'application/xaml+xml': ['xaml'], + 'application/xcap-diff+xml': ['xdf'], + 'application/xenc+xml': ['xenc'], + 'application/xhtml+xml': ['xhtml', 'xht'], + 'application/xml': ['xml', 'xsl', 'xsd', 'rng'], + 'application/xml-dtd': ['dtd'], + 'application/xop+xml': ['xop'], + 'application/xproc+xml': ['xpl'], + 'application/xslt+xml': ['xslt'], + 'application/xspf+xml': ['xspf'], + 'application/xv+xml': ['mxml', 'xhvml', 'xvml', 'xvm'], + 'application/yang': ['yang'], + 'application/yin+xml': ['yin'], + 'application/zip': ['zip'], + 'audio/3gpp': [], + 'audio/adpcm': ['adp'], + 'audio/basic': ['au', 'snd'], + 'audio/midi': ['mid', 'midi', 'kar', 'rmi'], + 'audio/mp3': [], + 'audio/mp4': ['m4a', 'mp4a'], + 'audio/mpeg': ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'], + 'audio/ogg': ['oga', 'ogg', 'spx'], + 'audio/s3m': ['s3m'], + 'audio/silk': ['sil'], + 'audio/vnd.dece.audio': ['uva', 'uvva'], + 'audio/vnd.digital-winds': ['eol'], + 'audio/vnd.dra': ['dra'], + 'audio/vnd.dts': ['dts'], + 'audio/vnd.dts.hd': ['dtshd'], + 'audio/vnd.lucent.voice': ['lvp'], + 'audio/vnd.ms-playready.media.pya': ['pya'], + 'audio/vnd.nuera.ecelp4800': ['ecelp4800'], + 'audio/vnd.nuera.ecelp7470': ['ecelp7470'], + 'audio/vnd.nuera.ecelp9600': ['ecelp9600'], + 'audio/vnd.rip': ['rip'], + 'audio/wav': ['wav'], + 'audio/wave': [], + 'audio/webm': ['weba'], + 'audio/x-aac': ['aac'], + 'audio/x-aiff': ['aif', 'aiff', 'aifc'], + 'audio/x-caf': ['caf'], + 'audio/x-flac': ['flac'], + 'audio/x-m4a': [], + 'audio/x-matroska': ['mka'], + 'audio/x-mpegurl': ['m3u'], + 'audio/x-ms-wax': ['wax'], + 'audio/x-ms-wma': ['wma'], + 'audio/x-pn-realaudio': ['ram', 'ra'], + 'audio/x-pn-realaudio-plugin': ['rmp'], + 'audio/x-realaudio': [], + 'audio/x-wav': [], + 'audio/xm': ['xm'], + 'chemical/x-cdx': ['cdx'], + 'chemical/x-cif': ['cif'], + 'chemical/x-cmdf': ['cmdf'], + 'chemical/x-cml': ['cml'], + 'chemical/x-csml': ['csml'], + 'chemical/x-xyz': ['xyz'], + 'font/collection': ['ttc'], + 'font/otf': ['otf'], + 'font/ttf': ['ttf'], + 'font/woff': ['woff'], + 'font/woff2': ['woff2'], + 'image/apng': ['apng'], + 'image/bmp': ['bmp'], + 'image/cgm': ['cgm'], + 'image/g3fax': ['g3'], + 'image/gif': ['gif'], + 'image/ief': ['ief'], + 'image/jp2': ['jp2', 'jpg2'], + 'image/jpeg': ['jpeg', 'jpg', 'jpe'], + 'image/jpm': ['jpm'], + 'image/jpx': ['jpx', 'jpf'], + 'image/ktx': ['ktx'], + 'image/png': ['png'], + 'image/prs.btif': ['btif'], + 'image/sgi': ['sgi'], + 'image/svg+xml': ['svg', 'svgz'], + 'image/tiff': ['tiff', 'tif'], + 'image/vnd.adobe.photoshop': ['psd'], + 'image/vnd.dece.graphic': ['uvi', 'uvvi', 'uvg', 'uvvg'], + 'image/vnd.djvu': ['djvu', 'djv'], + 'image/vnd.dvb.subtitle': [], + 'image/vnd.dwg': ['dwg'], + 'image/vnd.dxf': ['dxf'], + 'image/vnd.fastbidsheet': ['fbs'], + 'image/vnd.fpx': ['fpx'], + 'image/vnd.fst': ['fst'], + 'image/vnd.fujixerox.edmics-mmr': ['mmr'], + 'image/vnd.fujixerox.edmics-rlc': ['rlc'], + 'image/vnd.ms-modi': ['mdi'], + 'image/vnd.ms-photo': ['wdp'], + 'image/vnd.net-fpx': ['npx'], + 'image/vnd.wap.wbmp': ['wbmp'], + 'image/vnd.xiff': ['xif'], + 'image/webp': ['webp'], + 'image/x-3ds': ['3ds'], + 'image/x-cmu-raster': ['ras'], + 'image/x-cmx': ['cmx'], + 'image/x-freehand': ['fh', 'fhc', 'fh4', 'fh5', 'fh7'], + 'image/x-icon': ['ico'], + 'image/x-jng': ['jng'], + 'image/x-mrsid-image': ['sid'], + 'image/x-ms-bmp': [], + 'image/x-pcx': ['pcx'], + 'image/x-pict': ['pic', 'pct'], + 'image/x-portable-anymap': ['pnm'], + 'image/x-portable-bitmap': ['pbm'], + 'image/x-portable-graymap': ['pgm'], + 'image/x-portable-pixmap': ['ppm'], + 'image/x-rgb': ['rgb'], + 'image/x-tga': ['tga'], + 'image/x-xbitmap': ['xbm'], + 'image/x-xpixmap': ['xpm'], + 'image/x-xwindowdump': ['xwd'], + 'message/rfc822': ['eml', 'mime'], + 'model/gltf+json': ['gltf'], + 'model/gltf-binary': ['glb'], + 'model/iges': ['igs', 'iges'], + 'model/mesh': ['msh', 'mesh', 'silo'], + 'model/vnd.collada+xml': ['dae'], + 'model/vnd.dwf': ['dwf'], + 'model/vnd.gdl': ['gdl'], + 'model/vnd.gtw': ['gtw'], + 'model/vnd.mts': ['mts'], + 'model/vnd.vtu': ['vtu'], + 'model/vrml': ['wrl', 'vrml'], + 'model/x3d+binary': ['x3db', 'x3dbz'], + 'model/x3d+vrml': ['x3dv', 'x3dvz'], + 'model/x3d+xml': ['x3d', 'x3dz'], + 'text/cache-manifest': ['appcache', 'manifest'], + 'text/calendar': ['ics', 'ifb'], + 'text/coffeescript': ['coffee', 'litcoffee'], + 'text/css': ['css'], + 'text/csv': ['csv'], + 'text/hjson': ['hjson'], + 'text/html': ['html', 'htm', 'shtml'], + 'text/jade': ['jade'], + 'text/jsx': ['jsx'], + 'text/less': ['less'], + 'text/markdown': ['markdown', 'md'], + 'text/mathml': ['mml'], + 'text/n3': ['n3'], + 'text/plain': ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'ini'], + 'text/prs.lines.tag': ['dsc'], + 'text/richtext': ['rtx'], + 'text/rtf': [], + 'text/sgml': ['sgml', 'sgm'], + 'text/slim': ['slim', 'slm'], + 'text/stylus': ['stylus', 'styl'], + 'text/tab-separated-values': ['tsv'], + 'text/troff': ['t', 'tr', 'roff', 'man', 'me', 'ms'], + 'text/turtle': ['ttl'], + 'text/uri-list': ['uri', 'uris', 'urls'], + 'text/vcard': ['vcard'], + 'text/vnd.curl': ['curl'], + 'text/vnd.curl.dcurl': ['dcurl'], + 'text/vnd.curl.mcurl': ['mcurl'], + 'text/vnd.curl.scurl': ['scurl'], + 'text/vnd.dvb.subtitle': ['sub'], + 'text/vnd.fly': ['fly'], + 'text/vnd.fmi.flexstor': ['flx'], + 'text/vnd.graphviz': ['gv'], + 'text/vnd.in3d.3dml': ['3dml'], + 'text/vnd.in3d.spot': ['spot'], + 'text/vnd.sun.j2me.app-descriptor': ['jad'], + 'text/vnd.wap.wml': ['wml'], + 'text/vnd.wap.wmlscript': ['wmls'], + 'text/vtt': ['vtt'], + 'text/x-asm': ['s', 'asm'], + 'text/x-c': ['c', 'cc', 'cxx', 'cpp', 'h', 'hh', 'dic'], + 'text/x-component': ['htc'], + 'text/x-fortran': ['f', 'for', 'f77', 'f90'], + 'text/x-handlebars-template': ['hbs'], + 'text/x-java-source': ['java'], + 'text/x-lua': ['lua'], + 'text/x-markdown': ['mkd'], + 'text/x-nfo': ['nfo'], + 'text/x-opml': ['opml'], + 'text/x-org': [], + 'text/x-pascal': ['p', 'pas'], + 'text/x-processing': ['pde'], + 'text/x-sass': ['sass'], + 'text/x-scss': ['scss'], + 'text/x-setext': ['etx'], + 'text/x-sfv': ['sfv'], + 'text/x-suse-ymp': ['ymp'], + 'text/x-uuencode': ['uu'], + 'text/x-vcalendar': ['vcs'], + 'text/x-vcard': ['vcf'], + 'text/xml': [], + 'text/yaml': ['yaml', 'yml'], + 'video/3gpp': ['3gp', '3gpp'], + 'video/3gpp2': ['3g2'], + 'video/h261': ['h261'], + 'video/h263': ['h263'], + 'video/h264': ['h264'], + 'video/jpeg': ['jpgv'], + 'video/jpm': ['jpgm'], + 'video/mj2': ['mj2', 'mjp2'], + 'video/mp2t': ['ts'], + 'video/mp4': ['mp4', 'mp4v', 'mpg4'], + 'video/mpeg': ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v'], + 'video/ogg': ['ogv'], + 'video/quicktime': ['qt', 'mov'], + 'video/vnd.dece.hd': ['uvh', 'uvvh'], + 'video/vnd.dece.mobile': ['uvm', 'uvvm'], + 'video/vnd.dece.pd': ['uvp', 'uvvp'], + 'video/vnd.dece.sd': ['uvs', 'uvvs'], + 'video/vnd.dece.video': ['uvv', 'uvvv'], + 'video/vnd.dvb.file': ['dvb'], + 'video/vnd.fvt': ['fvt'], + 'video/vnd.mpegurl': ['mxu', 'm4u'], + 'video/vnd.ms-playready.media.pyv': ['pyv'], + 'video/vnd.uvvu.mp4': ['uvu', 'uvvu'], + 'video/vnd.vivo': ['viv'], + 'video/webm': ['webm'], + 'video/x-f4v': ['f4v'], + 'video/x-fli': ['fli'], + 'video/x-flv': ['flv'], + 'video/x-m4v': ['m4v'], + 'video/x-matroska': ['mkv', 'mk3d', 'mks'], + 'video/x-mng': ['mng'], + 'video/x-ms-asf': ['asf', 'asx'], + 'video/x-ms-vob': ['vob'], + 'video/x-ms-wm': ['wm'], + 'video/x-ms-wmv': ['wmv'], + 'video/x-ms-wmx': ['wmx'], + 'video/x-ms-wvx': ['wvx'], + 'video/x-msvideo': ['avi'], + 'video/x-sgi-movie': ['movie'], + 'video/x-smv': ['smv'], + 'x-conference/x-cooltalk': ['ice'], +}; + +module.exports = Object.entries(types).reduce( + (all, [type, exts]) => + Object.assign(all, ...exts.map((ext) => ({ [ext]: type }))), + {} +); diff --git a/ssl.conf b/config/ssl.conf similarity index 100% rename from ssl.conf rename to config/ssl.conf diff --git a/test/assets/exists.png b/example/assets/exists.png similarity index 100% rename from test/assets/exists.png rename to example/assets/exists.png diff --git a/test/assets/file with space.html b/example/assets/file with space.html similarity index 100% rename from test/assets/file with space.html rename to example/assets/file with space.html diff --git a/test/assets/index.css b/example/assets/index.css similarity index 100% rename from test/assets/index.css rename to example/assets/index.css diff --git a/test/assets/index.js b/example/assets/index.js similarity index 100% rename from test/assets/index.js rename to example/assets/index.js diff --git a/test/index.html b/example/index.html similarity index 100% rename from test/index.html rename to example/index.html diff --git a/test/index.js b/example/index.js similarity index 100% rename from test/index.js rename to example/index.js diff --git a/test/nested/assets/exists.png b/example/nested/assets/exists.png similarity index 100% rename from test/nested/assets/exists.png rename to example/nested/assets/exists.png diff --git a/test/nested/assets/index.css b/example/nested/assets/index.css similarity index 100% rename from test/nested/assets/index.css rename to example/nested/assets/index.css diff --git a/test/nested/assets/index.js b/example/nested/assets/index.js similarity index 100% rename from test/nested/assets/index.js rename to example/nested/assets/index.js diff --git a/test/nested/index.html b/example/nested/index.html similarity index 100% rename from test/nested/index.html rename to example/nested/index.html diff --git a/test/nested/index.js b/example/nested/index.js similarity index 100% rename from test/nested/index.js rename to example/nested/index.js diff --git a/servor.js b/servor.js index 6771e5c..ad2de72 100644 --- a/servor.js +++ b/servor.js @@ -9,6 +9,8 @@ const net = require('net'); const zlib = require('zlib'); const cwd = process.cwd(); +const mime = require('./config/mimetypes.js'); + const watch = process.platform !== 'linux' ? (x, cb) => fs.watch(x, { recursive: true }, cb) @@ -31,12 +33,6 @@ const ips = Object.values(os.networkInterfaces()) .filter((i) => i.family === 'IPv4' && i.internal === false) .map((i) => i.address); -const mimes = Object.entries(require('./types.json')).reduce( - (all, [type, exts]) => - Object.assign(all, ...exts.map((ext) => ({ [ext]: type }))), - {} -); - module.exports = async ({ root = '.', module = false, @@ -95,13 +91,12 @@ module.exports = async ({ const sendFile = (res, status, file, ext, encoding = 'binary') => { if (['js', 'css', 'html', 'json', 'xml', 'svg'].includes(ext)) { - res.removeHeader('Content-Length'); res.setHeader('Content-Encoding', 'gzip'); file = zlib.gzipSync(utf8(file)); encoding = 'utf8'; } res.writeHead(status, { - 'Content-Type': mimes[ext] || 'application/octet-stream', + 'Content-Type': mime[ext] || 'application/octet-stream', }); res.write(file, encoding); res.end(); @@ -123,6 +118,8 @@ module.exports = async ({ reloadClients.push(res); }; + // Respond to requests with a file extension + const serveStaticFile = (res, pathname) => { const uri = path.join(root, pathname); let ext = uri.replace(/^.*[\.\/\\]/, '').toLowerCase(); @@ -132,6 +129,8 @@ module.exports = async ({ ); }; + // Respond to requests without a file extension + const serveRoute = (res, pathname) => { const uri = routes ? path.join(root, pathname, fallback) @@ -173,11 +172,6 @@ module.exports = async ({ process.exit(); }); - return { - url: `${protocol}://localhost:${port}`, - root, - protocol, - port, - ips, - }; + const x = { url: `${protocol}://localhost:${port}` }; + return { ...x, root, protocol, port, ips }; }; diff --git a/test.js b/test.js index 2035623..d181a9b 100644 --- a/test.js +++ b/test.js @@ -21,7 +21,7 @@ const test = (cmd) => (url) => async (expect) => { ); // Run the command and wait for the server to start - const [c, ...a] = ('node cli test ' + cmd).trim().split(' '); + const [c, ...a] = ('node cli example ' + cmd).trim().split(' '); const servor = cp.spawn(c, a); const { origin } = await new Promise((resolve) => servor.stdout.once('data', (out) => { @@ -49,9 +49,9 @@ const test = (cmd) => (url) => async (expect) => { // Change a file to trigger reload let reload = false; if (cmd.includes('--reload')) { - modifyFile('test/index.html'); + modifyFile('example/index.html'); reload = await page.waitForNavigation({ timeout: 1000 }).catch(() => false); - modifyFile('test/assets/index.js'); + modifyFile('example/assets/index.js'); reload = await page.waitForNavigation({ timeout: 1000 }).catch(() => false); } diff --git a/types.json b/types.json deleted file mode 100644 index bec78ab..0000000 --- a/types.json +++ /dev/null @@ -1 +0,0 @@ -{"application/andrew-inset":["ez"],"application/applixware":["aw"],"application/atom+xml":["atom"],"application/atomcat+xml":["atomcat"],"application/atomsvc+xml":["atomsvc"],"application/bdoc":["bdoc"],"application/ccxml+xml":["ccxml"],"application/cdmi-capability":["cdmia"],"application/cdmi-container":["cdmic"],"application/cdmi-domain":["cdmid"],"application/cdmi-object":["cdmio"],"application/cdmi-queue":["cdmiq"],"application/cu-seeme":["cu"],"application/dash+xml":["mpd"],"application/davmount+xml":["davmount"],"application/docbook+xml":["dbk"],"application/dssc+der":["dssc"],"application/dssc+xml":["xdssc"],"application/ecmascript":["ecma"],"application/emma+xml":["emma"],"application/epub+zip":["epub"],"application/exi":["exi"],"application/font-tdpfr":["pfr"],"application/font-woff":[],"application/font-woff2":[],"application/geo+json":["geojson"],"application/gml+xml":["gml"],"application/gpx+xml":["gpx"],"application/gxf":["gxf"],"application/gzip":["gz"],"application/hyperstudio":["stk"],"application/inkml+xml":["ink","inkml"],"application/ipfix":["ipfix"],"application/java-archive":["jar","war","ear"],"application/java-serialized-object":["ser"],"application/java-vm":["class"],"application/javascript":["js","mjs"],"application/json":["json","map"],"application/json5":["json5"],"application/jsonml+json":["jsonml"],"application/ld+json":["jsonld"],"application/lost+xml":["lostxml"],"application/mac-binhex40":["hqx"],"application/mac-compactpro":["cpt"],"application/mads+xml":["mads"],"application/manifest+json":["webmanifest"],"application/marc":["mrc"],"application/marcxml+xml":["mrcx"],"application/mathematica":["ma","nb","mb"],"application/mathml+xml":["mathml"],"application/mbox":["mbox"],"application/mediaservercontrol+xml":["mscml"],"application/metalink+xml":["metalink"],"application/metalink4+xml":["meta4"],"application/mets+xml":["mets"],"application/mods+xml":["mods"],"application/mp21":["m21","mp21"],"application/mp4":["mp4s","m4p"],"application/msword":["doc","dot"],"application/mxf":["mxf"],"application/octet-stream":["bin","dms","lrf","mar","so","dist","distz","pkg","bpk","dump","elc","deploy","exe","dll","deb","dmg","iso","img","msi","msp","msm","buffer"],"application/oda":["oda"],"application/oebps-package+xml":["opf"],"application/ogg":["ogx"],"application/omdoc+xml":["omdoc"],"application/onenote":["onetoc","onetoc2","onetmp","onepkg"],"application/oxps":["oxps"],"application/patch-ops-error+xml":["xer"],"application/pdf":["pdf"],"application/pgp-encrypted":["pgp"],"application/pgp-signature":["asc","sig"],"application/pics-rules":["prf"],"application/pkcs10":["p10"],"application/pkcs7-mime":["p7m","p7c"],"application/pkcs7-signature":["p7s"],"application/pkcs8":["p8"],"application/pkix-attr-cert":["ac"],"application/pkix-cert":["cer"],"application/pkix-crl":["crl"],"application/pkix-pkipath":["pkipath"],"application/pkixcmp":["pki"],"application/pls+xml":["pls"],"application/postscript":["ai","eps","ps"],"application/prs.cww":["cww"],"application/pskc+xml":["pskcxml"],"application/raml+yaml":["raml"],"application/rdf+xml":["rdf"],"application/reginfo+xml":["rif"],"application/relax-ng-compact-syntax":["rnc"],"application/resource-lists+xml":["rl"],"application/resource-lists-diff+xml":["rld"],"application/rls-services+xml":["rs"],"application/rpki-ghostbusters":["gbr"],"application/rpki-manifest":["mft"],"application/rpki-roa":["roa"],"application/rsd+xml":["rsd"],"application/rss+xml":["rss"],"application/rtf":["rtf"],"application/sbml+xml":["sbml"],"application/scvp-cv-request":["scq"],"application/scvp-cv-response":["scs"],"application/scvp-vp-request":["spq"],"application/scvp-vp-response":["spp"],"application/sdp":["sdp"],"application/set-payment-initiation":["setpay"],"application/set-registration-initiation":["setreg"],"application/shf+xml":["shf"],"application/smil+xml":["smi","smil"],"application/sparql-query":["rq"],"application/sparql-results+xml":["srx"],"application/srgs":["gram"],"application/srgs+xml":["grxml"],"application/sru+xml":["sru"],"application/ssdl+xml":["ssdl"],"application/ssml+xml":["ssml"],"application/tei+xml":["tei","teicorpus"],"application/thraud+xml":["tfi"],"application/timestamped-data":["tsd"],"application/vnd.3gpp.pic-bw-large":["plb"],"application/vnd.3gpp.pic-bw-small":["psb"],"application/vnd.3gpp.pic-bw-var":["pvb"],"application/vnd.3gpp2.tcap":["tcap"],"application/vnd.3m.post-it-notes":["pwn"],"application/vnd.accpac.simply.aso":["aso"],"application/vnd.accpac.simply.imp":["imp"],"application/vnd.acucobol":["acu"],"application/vnd.acucorp":["atc","acutc"],"application/vnd.adobe.air-application-installer-package+zip":["air"],"application/vnd.adobe.formscentral.fcdt":["fcdt"],"application/vnd.adobe.fxp":["fxp","fxpl"],"application/vnd.adobe.xdp+xml":["xdp"],"application/vnd.adobe.xfdf":["xfdf"],"application/vnd.ahead.space":["ahead"],"application/vnd.airzip.filesecure.azf":["azf"],"application/vnd.airzip.filesecure.azs":["azs"],"application/vnd.amazon.ebook":["azw"],"application/vnd.americandynamics.acc":["acc"],"application/vnd.amiga.ami":["ami"],"application/vnd.android.package-archive":["apk"],"application/vnd.anser-web-certificate-issue-initiation":["cii"],"application/vnd.anser-web-funds-transfer-initiation":["fti"],"application/vnd.antix.game-component":["atx"],"application/vnd.apple.installer+xml":["mpkg"],"application/vnd.apple.mpegurl":["m3u8"],"application/vnd.apple.pkpass":["pkpass"],"application/vnd.aristanetworks.swi":["swi"],"application/vnd.astraea-software.iota":["iota"],"application/vnd.audiograph":["aep"],"application/vnd.blueice.multipass":["mpm"],"application/vnd.bmi":["bmi"],"application/vnd.businessobjects":["rep"],"application/vnd.chemdraw+xml":["cdxml"],"application/vnd.chipnuts.karaoke-mmd":["mmd"],"application/vnd.cinderella":["cdy"],"application/vnd.claymore":["cla"],"application/vnd.cloanto.rp9":["rp9"],"application/vnd.clonk.c4group":["c4g","c4d","c4f","c4p","c4u"],"application/vnd.cluetrust.cartomobile-config":["c11amc"],"application/vnd.cluetrust.cartomobile-config-pkg":["c11amz"],"application/vnd.commonspace":["csp"],"application/vnd.contact.cmsg":["cdbcmsg"],"application/vnd.cosmocaller":["cmc"],"application/vnd.crick.clicker":["clkx"],"application/vnd.crick.clicker.keyboard":["clkk"],"application/vnd.crick.clicker.palette":["clkp"],"application/vnd.crick.clicker.template":["clkt"],"application/vnd.crick.clicker.wordbank":["clkw"],"application/vnd.criticaltools.wbs+xml":["wbs"],"application/vnd.ctc-posml":["pml"],"application/vnd.cups-ppd":["ppd"],"application/vnd.curl.car":["car"],"application/vnd.curl.pcurl":["pcurl"],"application/vnd.dart":["dart"],"application/vnd.data-vision.rdz":["rdz"],"application/vnd.dece.data":["uvf","uvvf","uvd","uvvd"],"application/vnd.dece.ttml+xml":["uvt","uvvt"],"application/vnd.dece.unspecified":["uvx","uvvx"],"application/vnd.dece.zip":["uvz","uvvz"],"application/vnd.denovo.fcselayout-link":["fe_launch"],"application/vnd.dna":["dna"],"application/vnd.dolby.mlp":["mlp"],"application/vnd.dpgraph":["dpg"],"application/vnd.dreamfactory":["dfac"],"application/vnd.ds-keypoint":["kpxx"],"application/vnd.dvb.ait":["ait"],"application/vnd.dvb.service":["svc"],"application/vnd.dynageo":["geo"],"application/vnd.ecowin.chart":["mag"],"application/vnd.enliven":["nml"],"application/vnd.epson.esf":["esf"],"application/vnd.epson.msf":["msf"],"application/vnd.epson.quickanime":["qam"],"application/vnd.epson.salt":["slt"],"application/vnd.epson.ssf":["ssf"],"application/vnd.eszigno3+xml":["es3","et3"],"application/vnd.ezpix-album":["ez2"],"application/vnd.ezpix-package":["ez3"],"application/vnd.fdf":["fdf"],"application/vnd.fdsn.mseed":["mseed"],"application/vnd.fdsn.seed":["seed","dataless"],"application/vnd.flographit":["gph"],"application/vnd.fluxtime.clip":["ftc"],"application/vnd.framemaker":["fm","frame","maker","book"],"application/vnd.frogans.fnc":["fnc"],"application/vnd.frogans.ltf":["ltf"],"application/vnd.fsc.weblaunch":["fsc"],"application/vnd.fujitsu.oasys":["oas"],"application/vnd.fujitsu.oasys2":["oa2"],"application/vnd.fujitsu.oasys3":["oa3"],"application/vnd.fujitsu.oasysgp":["fg5"],"application/vnd.fujitsu.oasysprs":["bh2"],"application/vnd.fujixerox.ddd":["ddd"],"application/vnd.fujixerox.docuworks":["xdw"],"application/vnd.fujixerox.docuworks.binder":["xbd"],"application/vnd.fuzzysheet":["fzs"],"application/vnd.genomatix.tuxedo":["txd"],"application/vnd.geogebra.file":["ggb"],"application/vnd.geogebra.tool":["ggt"],"application/vnd.geometry-explorer":["gex","gre"],"application/vnd.geonext":["gxt"],"application/vnd.geoplan":["g2w"],"application/vnd.geospace":["g3w"],"application/vnd.gmx":["gmx"],"application/vnd.google-apps.document":["gdoc"],"application/vnd.google-apps.presentation":["gslides"],"application/vnd.google-apps.spreadsheet":["gsheet"],"application/vnd.google-earth.kml+xml":["kml"],"application/vnd.google-earth.kmz":["kmz"],"application/vnd.grafeq":["gqf","gqs"],"application/vnd.groove-account":["gac"],"application/vnd.groove-help":["ghf"],"application/vnd.groove-identity-message":["gim"],"application/vnd.groove-injector":["grv"],"application/vnd.groove-tool-message":["gtm"],"application/vnd.groove-tool-template":["tpl"],"application/vnd.groove-vcard":["vcg"],"application/vnd.hal+xml":["hal"],"application/vnd.handheld-entertainment+xml":["zmm"],"application/vnd.hbci":["hbci"],"application/vnd.hhe.lesson-player":["les"],"application/vnd.hp-hpgl":["hpgl"],"application/vnd.hp-hpid":["hpid"],"application/vnd.hp-hps":["hps"],"application/vnd.hp-jlyt":["jlt"],"application/vnd.hp-pcl":["pcl"],"application/vnd.hp-pclxl":["pclxl"],"application/vnd.hydrostatix.sof-data":["sfd-hdstx"],"application/vnd.ibm.minipay":["mpy"],"application/vnd.ibm.modcap":["afp","listafp","list3820"],"application/vnd.ibm.rights-management":["irm"],"application/vnd.ibm.secure-container":["sc"],"application/vnd.iccprofile":["icc","icm"],"application/vnd.igloader":["igl"],"application/vnd.immervision-ivp":["ivp"],"application/vnd.immervision-ivu":["ivu"],"application/vnd.insors.igm":["igm"],"application/vnd.intercon.formnet":["xpw","xpx"],"application/vnd.intergeo":["i2g"],"application/vnd.intu.qbo":["qbo"],"application/vnd.intu.qfx":["qfx"],"application/vnd.ipunplugged.rcprofile":["rcprofile"],"application/vnd.irepository.package+xml":["irp"],"application/vnd.is-xpr":["xpr"],"application/vnd.isac.fcs":["fcs"],"application/vnd.jam":["jam"],"application/vnd.jcp.javame.midlet-rms":["rms"],"application/vnd.jisp":["jisp"],"application/vnd.joost.joda-archive":["joda"],"application/vnd.kahootz":["ktz","ktr"],"application/vnd.kde.karbon":["karbon"],"application/vnd.kde.kchart":["chrt"],"application/vnd.kde.kformula":["kfo"],"application/vnd.kde.kivio":["flw"],"application/vnd.kde.kontour":["kon"],"application/vnd.kde.kpresenter":["kpr","kpt"],"application/vnd.kde.kspread":["ksp"],"application/vnd.kde.kword":["kwd","kwt"],"application/vnd.kenameaapp":["htke"],"application/vnd.kidspiration":["kia"],"application/vnd.kinar":["kne","knp"],"application/vnd.koan":["skp","skd","skt","skm"],"application/vnd.kodak-descriptor":["sse"],"application/vnd.las.las+xml":["lasxml"],"application/vnd.llamagraphics.life-balance.desktop":["lbd"],"application/vnd.llamagraphics.life-balance.exchange+xml":["lbe"],"application/vnd.lotus-1-2-3":["123"],"application/vnd.lotus-approach":["apr"],"application/vnd.lotus-freelance":["pre"],"application/vnd.lotus-notes":["nsf"],"application/vnd.lotus-organizer":["org"],"application/vnd.lotus-screencam":["scm"],"application/vnd.lotus-wordpro":["lwp"],"application/vnd.macports.portpkg":["portpkg"],"application/vnd.mcd":["mcd"],"application/vnd.medcalcdata":["mc1"],"application/vnd.mediastation.cdkey":["cdkey"],"application/vnd.mfer":["mwf"],"application/vnd.mfmp":["mfm"],"application/vnd.micrografx.flo":["flo"],"application/vnd.micrografx.igx":["igx"],"application/vnd.mif":["mif"],"application/vnd.mobius.daf":["daf"],"application/vnd.mobius.dis":["dis"],"application/vnd.mobius.mbk":["mbk"],"application/vnd.mobius.mqy":["mqy"],"application/vnd.mobius.msl":["msl"],"application/vnd.mobius.plc":["plc"],"application/vnd.mobius.txf":["txf"],"application/vnd.mophun.application":["mpn"],"application/vnd.mophun.certificate":["mpc"],"application/vnd.mozilla.xul+xml":["xul"],"application/vnd.ms-artgalry":["cil"],"application/vnd.ms-cab-compressed":["cab"],"application/vnd.ms-excel":["xls","xlm","xla","xlc","xlt","xlw"],"application/vnd.ms-excel.addin.macroenabled.12":["xlam"],"application/vnd.ms-excel.sheet.binary.macroenabled.12":["xlsb"],"application/vnd.ms-excel.sheet.macroenabled.12":["xlsm"],"application/vnd.ms-excel.template.macroenabled.12":["xltm"],"application/vnd.ms-fontobject":["eot"],"application/vnd.ms-htmlhelp":["chm"],"application/vnd.ms-ims":["ims"],"application/vnd.ms-lrm":["lrm"],"application/vnd.ms-officetheme":["thmx"],"application/vnd.ms-outlook":["msg"],"application/vnd.ms-pki.seccat":["cat"],"application/vnd.ms-pki.stl":["stl"],"application/vnd.ms-powerpoint":["ppt","pps","pot"],"application/vnd.ms-powerpoint.addin.macroenabled.12":["ppam"],"application/vnd.ms-powerpoint.presentation.macroenabled.12":["pptm"],"application/vnd.ms-powerpoint.slide.macroenabled.12":["sldm"],"application/vnd.ms-powerpoint.slideshow.macroenabled.12":["ppsm"],"application/vnd.ms-powerpoint.template.macroenabled.12":["potm"],"application/vnd.ms-project":["mpp","mpt"],"application/vnd.ms-word.document.macroenabled.12":["docm"],"application/vnd.ms-word.template.macroenabled.12":["dotm"],"application/vnd.ms-works":["wps","wks","wcm","wdb"],"application/vnd.ms-wpl":["wpl"],"application/vnd.ms-xpsdocument":["xps"],"application/vnd.mseq":["mseq"],"application/vnd.musician":["mus"],"application/vnd.muvee.style":["msty"],"application/vnd.mynfc":["taglet"],"application/vnd.neurolanguage.nlu":["nlu"],"application/vnd.nitf":["ntf","nitf"],"application/vnd.noblenet-directory":["nnd"],"application/vnd.noblenet-sealer":["nns"],"application/vnd.noblenet-web":["nnw"],"application/vnd.nokia.n-gage.data":["ngdat"],"application/vnd.nokia.n-gage.symbian.install":["n-gage"],"application/vnd.nokia.radio-preset":["rpst"],"application/vnd.nokia.radio-presets":["rpss"],"application/vnd.novadigm.edm":["edm"],"application/vnd.novadigm.edx":["edx"],"application/vnd.novadigm.ext":["ext"],"application/vnd.oasis.opendocument.chart":["odc"],"application/vnd.oasis.opendocument.chart-template":["otc"],"application/vnd.oasis.opendocument.database":["odb"],"application/vnd.oasis.opendocument.formula":["odf"],"application/vnd.oasis.opendocument.formula-template":["odft"],"application/vnd.oasis.opendocument.graphics":["odg"],"application/vnd.oasis.opendocument.graphics-template":["otg"],"application/vnd.oasis.opendocument.image":["odi"],"application/vnd.oasis.opendocument.image-template":["oti"],"application/vnd.oasis.opendocument.presentation":["odp"],"application/vnd.oasis.opendocument.presentation-template":["otp"],"application/vnd.oasis.opendocument.spreadsheet":["ods"],"application/vnd.oasis.opendocument.spreadsheet-template":["ots"],"application/vnd.oasis.opendocument.text":["odt"],"application/vnd.oasis.opendocument.text-master":["odm"],"application/vnd.oasis.opendocument.text-template":["ott"],"application/vnd.oasis.opendocument.text-web":["oth"],"application/vnd.olpc-sugar":["xo"],"application/vnd.oma.dd2+xml":["dd2"],"application/vnd.openofficeorg.extension":["oxt"],"application/vnd.openxmlformats-officedocument.presentationml.presentation":["pptx"],"application/vnd.openxmlformats-officedocument.presentationml.slide":["sldx"],"application/vnd.openxmlformats-officedocument.presentationml.slideshow":["ppsx"],"application/vnd.openxmlformats-officedocument.presentationml.template":["potx"],"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":["xlsx"],"application/vnd.openxmlformats-officedocument.spreadsheetml.template":["xltx"],"application/vnd.openxmlformats-officedocument.wordprocessingml.document":["docx"],"application/vnd.openxmlformats-officedocument.wordprocessingml.template":["dotx"],"application/vnd.osgeo.mapguide.package":["mgp"],"application/vnd.osgi.dp":["dp"],"application/vnd.osgi.subsystem":["esa"],"application/vnd.palm":["pdb","pqa","oprc"],"application/vnd.pawaafile":["paw"],"application/vnd.pg.format":["str"],"application/vnd.pg.osasli":["ei6"],"application/vnd.picsel":["efif"],"application/vnd.pmi.widget":["wg"],"application/vnd.pocketlearn":["plf"],"application/vnd.powerbuilder6":["pbd"],"application/vnd.previewsystems.box":["box"],"application/vnd.proteus.magazine":["mgz"],"application/vnd.publishare-delta-tree":["qps"],"application/vnd.pvi.ptid1":["ptid"],"application/vnd.quark.quarkxpress":["qxd","qxt","qwd","qwt","qxl","qxb"],"application/vnd.realvnc.bed":["bed"],"application/vnd.recordare.musicxml":["mxl"],"application/vnd.recordare.musicxml+xml":["musicxml"],"application/vnd.rig.cryptonote":["cryptonote"],"application/vnd.rim.cod":["cod"],"application/vnd.rn-realmedia":["rm"],"application/vnd.rn-realmedia-vbr":["rmvb"],"application/vnd.route66.link66+xml":["link66"],"application/vnd.sailingtracker.track":["st"],"application/vnd.seemail":["see"],"application/vnd.sema":["sema"],"application/vnd.semd":["semd"],"application/vnd.semf":["semf"],"application/vnd.shana.informed.formdata":["ifm"],"application/vnd.shana.informed.formtemplate":["itp"],"application/vnd.shana.informed.interchange":["iif"],"application/vnd.shana.informed.package":["ipk"],"application/vnd.simtech-mindmapper":["twd","twds"],"application/vnd.smaf":["mmf"],"application/vnd.smart.teacher":["teacher"],"application/vnd.solent.sdkm+xml":["sdkm","sdkd"],"application/vnd.spotfire.dxp":["dxp"],"application/vnd.spotfire.sfs":["sfs"],"application/vnd.stardivision.calc":["sdc"],"application/vnd.stardivision.draw":["sda"],"application/vnd.stardivision.impress":["sdd"],"application/vnd.stardivision.math":["smf"],"application/vnd.stardivision.writer":["sdw","vor"],"application/vnd.stardivision.writer-global":["sgl"],"application/vnd.stepmania.package":["smzip"],"application/vnd.stepmania.stepchart":["sm"],"application/vnd.sun.wadl+xml":["wadl"],"application/vnd.sun.xml.calc":["sxc"],"application/vnd.sun.xml.calc.template":["stc"],"application/vnd.sun.xml.draw":["sxd"],"application/vnd.sun.xml.draw.template":["std"],"application/vnd.sun.xml.impress":["sxi"],"application/vnd.sun.xml.impress.template":["sti"],"application/vnd.sun.xml.math":["sxm"],"application/vnd.sun.xml.writer":["sxw"],"application/vnd.sun.xml.writer.global":["sxg"],"application/vnd.sun.xml.writer.template":["stw"],"application/vnd.sus-calendar":["sus","susp"],"application/vnd.svd":["svd"],"application/vnd.symbian.install":["sis","sisx"],"application/vnd.syncml+xml":["xsm"],"application/vnd.syncml.dm+wbxml":["bdm"],"application/vnd.syncml.dm+xml":["xdm"],"application/vnd.tao.intent-module-archive":["tao"],"application/vnd.tcpdump.pcap":["pcap","cap","dmp"],"application/vnd.tmobile-livetv":["tmo"],"application/vnd.trid.tpt":["tpt"],"application/vnd.triscape.mxs":["mxs"],"application/vnd.trueapp":["tra"],"application/vnd.ufdl":["ufd","ufdl"],"application/vnd.uiq.theme":["utz"],"application/vnd.umajin":["umj"],"application/vnd.unity":["unityweb"],"application/vnd.uoml+xml":["uoml"],"application/vnd.vcx":["vcx"],"application/vnd.visio":["vsd","vst","vss","vsw"],"application/vnd.visionary":["vis"],"application/vnd.vsf":["vsf"],"application/vnd.wap.wbxml":["wbxml"],"application/vnd.wap.wmlc":["wmlc"],"application/vnd.wap.wmlscriptc":["wmlsc"],"application/vnd.webturbo":["wtb"],"application/vnd.wolfram.player":["nbp"],"application/vnd.wordperfect":["wpd"],"application/vnd.wqd":["wqd"],"application/vnd.wt.stf":["stf"],"application/vnd.xara":["xar"],"application/vnd.xfdl":["xfdl"],"application/vnd.yamaha.hv-dic":["hvd"],"application/vnd.yamaha.hv-script":["hvs"],"application/vnd.yamaha.hv-voice":["hvp"],"application/vnd.yamaha.openscoreformat":["osf"],"application/vnd.yamaha.openscoreformat.osfpvg+xml":["osfpvg"],"application/vnd.yamaha.smaf-audio":["saf"],"application/vnd.yamaha.smaf-phrase":["spf"],"application/vnd.yellowriver-custom-menu":["cmp"],"application/vnd.zul":["zir","zirz"],"application/vnd.zzazz.deck+xml":["zaz"],"application/voicexml+xml":["vxml"],"application/wasm":["wasm"],"application/widget":["wgt"],"application/winhlp":["hlp"],"application/wsdl+xml":["wsdl"],"application/wspolicy+xml":["wspolicy"],"application/x-7z-compressed":["7z"],"application/x-abiword":["abw"],"application/x-ace-compressed":["ace"],"application/x-apple-diskimage":[],"application/x-arj":["arj"],"application/x-authorware-bin":["aab","x32","u32","vox"],"application/x-authorware-map":["aam"],"application/x-authorware-seg":["aas"],"application/x-bcpio":["bcpio"],"application/x-bdoc":[],"application/x-bittorrent":["torrent"],"application/x-blorb":["blb","blorb"],"application/x-bzip":["bz"],"application/x-bzip2":["bz2","boz"],"application/x-cbr":["cbr","cba","cbt","cbz","cb7"],"application/x-cdlink":["vcd"],"application/x-cfs-compressed":["cfs"],"application/x-chat":["chat"],"application/x-chess-pgn":["pgn"],"application/x-chrome-extension":["crx"],"application/x-cocoa":["cco"],"application/x-conference":["nsc"],"application/x-cpio":["cpio"],"application/x-csh":["csh"],"application/x-debian-package":["udeb"],"application/x-dgc-compressed":["dgc"],"application/x-director":["dir","dcr","dxr","cst","cct","cxt","w3d","fgd","swa"],"application/x-doom":["wad"],"application/x-dtbncx+xml":["ncx"],"application/x-dtbook+xml":["dtb"],"application/x-dtbresource+xml":["res"],"application/x-dvi":["dvi"],"application/x-envoy":["evy"],"application/x-eva":["eva"],"application/x-font-bdf":["bdf"],"application/x-font-ghostscript":["gsf"],"application/x-font-linux-psf":["psf"],"application/x-font-pcf":["pcf"],"application/x-font-snf":["snf"],"application/x-font-type1":["pfa","pfb","pfm","afm"],"application/x-freearc":["arc"],"application/x-futuresplash":["spl"],"application/x-gca-compressed":["gca"],"application/x-glulx":["ulx"],"application/x-gnumeric":["gnumeric"],"application/x-gramps-xml":["gramps"],"application/x-gtar":["gtar"],"application/x-hdf":["hdf"],"application/x-httpd-php":["php"],"application/x-install-instructions":["install"],"application/x-iso9660-image":[],"application/x-java-archive-diff":["jardiff"],"application/x-java-jnlp-file":["jnlp"],"application/x-latex":["latex"],"application/x-lua-bytecode":["luac"],"application/x-lzh-compressed":["lzh","lha"],"application/x-makeself":["run"],"application/x-mie":["mie"],"application/x-mobipocket-ebook":["prc","mobi"],"application/x-ms-application":["application"],"application/x-ms-shortcut":["lnk"],"application/x-ms-wmd":["wmd"],"application/x-ms-wmz":["wmz"],"application/x-ms-xbap":["xbap"],"application/x-msaccess":["mdb"],"application/x-msbinder":["obd"],"application/x-mscardfile":["crd"],"application/x-msclip":["clp"],"application/x-msdos-program":[],"application/x-msdownload":["com","bat"],"application/x-msmediaview":["mvb","m13","m14"],"application/x-msmetafile":["wmf","emf","emz"],"application/x-msmoney":["mny"],"application/x-mspublisher":["pub"],"application/x-msschedule":["scd"],"application/x-msterminal":["trm"],"application/x-mswrite":["wri"],"application/x-netcdf":["nc","cdf"],"application/x-ns-proxy-autoconfig":["pac"],"application/x-nzb":["nzb"],"application/x-perl":["pl","pm"],"application/x-pilot":[],"application/x-pkcs12":["p12","pfx"],"application/x-pkcs7-certificates":["p7b","spc"],"application/x-pkcs7-certreqresp":["p7r"],"application/x-rar-compressed":["rar"],"application/x-redhat-package-manager":["rpm"],"application/x-research-info-systems":["ris"],"application/x-sea":["sea"],"application/x-sh":["sh"],"application/x-shar":["shar"],"application/x-shockwave-flash":["swf"],"application/x-silverlight-app":["xap"],"application/x-sql":["sql"],"application/x-stuffit":["sit"],"application/x-stuffitx":["sitx"],"application/x-subrip":["srt"],"application/x-sv4cpio":["sv4cpio"],"application/x-sv4crc":["sv4crc"],"application/x-t3vm-image":["t3"],"application/x-tads":["gam"],"application/x-tar":["tar"],"application/x-tcl":["tcl","tk"],"application/x-tex":["tex"],"application/x-tex-tfm":["tfm"],"application/x-texinfo":["texinfo","texi"],"application/x-tgif":["obj"],"application/x-ustar":["ustar"],"application/x-virtualbox-hdd":["hdd"],"application/x-virtualbox-ova":["ova"],"application/x-virtualbox-ovf":["ovf"],"application/x-virtualbox-vbox":["vbox"],"application/x-virtualbox-vbox-extpack":["vbox-extpack"],"application/x-virtualbox-vdi":["vdi"],"application/x-virtualbox-vhd":["vhd"],"application/x-virtualbox-vmdk":["vmdk"],"application/x-wais-source":["src"],"application/x-web-app-manifest+json":["webapp"],"application/x-x509-ca-cert":["der","crt","pem"],"application/x-xfig":["fig"],"application/x-xliff+xml":["xlf"],"application/x-xpinstall":["xpi"],"application/x-xz":["xz"],"application/x-zmachine":["z1","z2","z3","z4","z5","z6","z7","z8"],"application/xaml+xml":["xaml"],"application/xcap-diff+xml":["xdf"],"application/xenc+xml":["xenc"],"application/xhtml+xml":["xhtml","xht"],"application/xml":["xml","xsl","xsd","rng"],"application/xml-dtd":["dtd"],"application/xop+xml":["xop"],"application/xproc+xml":["xpl"],"application/xslt+xml":["xslt"],"application/xspf+xml":["xspf"],"application/xv+xml":["mxml","xhvml","xvml","xvm"],"application/yang":["yang"],"application/yin+xml":["yin"],"application/zip":["zip"],"audio/3gpp":[],"audio/adpcm":["adp"],"audio/basic":["au","snd"],"audio/midi":["mid","midi","kar","rmi"],"audio/mp3":[],"audio/mp4":["m4a","mp4a"],"audio/mpeg":["mpga","mp2","mp2a","mp3","m2a","m3a"],"audio/ogg":["oga","ogg","spx"],"audio/s3m":["s3m"],"audio/silk":["sil"],"audio/vnd.dece.audio":["uva","uvva"],"audio/vnd.digital-winds":["eol"],"audio/vnd.dra":["dra"],"audio/vnd.dts":["dts"],"audio/vnd.dts.hd":["dtshd"],"audio/vnd.lucent.voice":["lvp"],"audio/vnd.ms-playready.media.pya":["pya"],"audio/vnd.nuera.ecelp4800":["ecelp4800"],"audio/vnd.nuera.ecelp7470":["ecelp7470"],"audio/vnd.nuera.ecelp9600":["ecelp9600"],"audio/vnd.rip":["rip"],"audio/wav":["wav"],"audio/wave":[],"audio/webm":["weba"],"audio/x-aac":["aac"],"audio/x-aiff":["aif","aiff","aifc"],"audio/x-caf":["caf"],"audio/x-flac":["flac"],"audio/x-m4a":[],"audio/x-matroska":["mka"],"audio/x-mpegurl":["m3u"],"audio/x-ms-wax":["wax"],"audio/x-ms-wma":["wma"],"audio/x-pn-realaudio":["ram","ra"],"audio/x-pn-realaudio-plugin":["rmp"],"audio/x-realaudio":[],"audio/x-wav":[],"audio/xm":["xm"],"chemical/x-cdx":["cdx"],"chemical/x-cif":["cif"],"chemical/x-cmdf":["cmdf"],"chemical/x-cml":["cml"],"chemical/x-csml":["csml"],"chemical/x-xyz":["xyz"],"font/collection":["ttc"],"font/otf":["otf"],"font/ttf":["ttf"],"font/woff":["woff"],"font/woff2":["woff2"],"image/apng":["apng"],"image/bmp":["bmp"],"image/cgm":["cgm"],"image/g3fax":["g3"],"image/gif":["gif"],"image/ief":["ief"],"image/jp2":["jp2","jpg2"],"image/jpeg":["jpeg","jpg","jpe"],"image/jpm":["jpm"],"image/jpx":["jpx","jpf"],"image/ktx":["ktx"],"image/png":["png"],"image/prs.btif":["btif"],"image/sgi":["sgi"],"image/svg+xml":["svg","svgz"],"image/tiff":["tiff","tif"],"image/vnd.adobe.photoshop":["psd"],"image/vnd.dece.graphic":["uvi","uvvi","uvg","uvvg"],"image/vnd.djvu":["djvu","djv"],"image/vnd.dvb.subtitle":[],"image/vnd.dwg":["dwg"],"image/vnd.dxf":["dxf"],"image/vnd.fastbidsheet":["fbs"],"image/vnd.fpx":["fpx"],"image/vnd.fst":["fst"],"image/vnd.fujixerox.edmics-mmr":["mmr"],"image/vnd.fujixerox.edmics-rlc":["rlc"],"image/vnd.ms-modi":["mdi"],"image/vnd.ms-photo":["wdp"],"image/vnd.net-fpx":["npx"],"image/vnd.wap.wbmp":["wbmp"],"image/vnd.xiff":["xif"],"image/webp":["webp"],"image/x-3ds":["3ds"],"image/x-cmu-raster":["ras"],"image/x-cmx":["cmx"],"image/x-freehand":["fh","fhc","fh4","fh5","fh7"],"image/x-icon":["ico"],"image/x-jng":["jng"],"image/x-mrsid-image":["sid"],"image/x-ms-bmp":[],"image/x-pcx":["pcx"],"image/x-pict":["pic","pct"],"image/x-portable-anymap":["pnm"],"image/x-portable-bitmap":["pbm"],"image/x-portable-graymap":["pgm"],"image/x-portable-pixmap":["ppm"],"image/x-rgb":["rgb"],"image/x-tga":["tga"],"image/x-xbitmap":["xbm"],"image/x-xpixmap":["xpm"],"image/x-xwindowdump":["xwd"],"message/rfc822":["eml","mime"],"model/gltf+json":["gltf"],"model/gltf-binary":["glb"],"model/iges":["igs","iges"],"model/mesh":["msh","mesh","silo"],"model/vnd.collada+xml":["dae"],"model/vnd.dwf":["dwf"],"model/vnd.gdl":["gdl"],"model/vnd.gtw":["gtw"],"model/vnd.mts":["mts"],"model/vnd.vtu":["vtu"],"model/vrml":["wrl","vrml"],"model/x3d+binary":["x3db","x3dbz"],"model/x3d+vrml":["x3dv","x3dvz"],"model/x3d+xml":["x3d","x3dz"],"text/cache-manifest":["appcache","manifest"],"text/calendar":["ics","ifb"],"text/coffeescript":["coffee","litcoffee"],"text/css":["css"],"text/csv":["csv"],"text/hjson":["hjson"],"text/html":["html","htm","shtml"],"text/jade":["jade"],"text/jsx":["jsx"],"text/less":["less"],"text/markdown":["markdown","md"],"text/mathml":["mml"],"text/n3":["n3"],"text/plain":["txt","text","conf","def","list","log","in","ini"],"text/prs.lines.tag":["dsc"],"text/richtext":["rtx"],"text/rtf":[],"text/sgml":["sgml","sgm"],"text/slim":["slim","slm"],"text/stylus":["stylus","styl"],"text/tab-separated-values":["tsv"],"text/troff":["t","tr","roff","man","me","ms"],"text/turtle":["ttl"],"text/uri-list":["uri","uris","urls"],"text/vcard":["vcard"],"text/vnd.curl":["curl"],"text/vnd.curl.dcurl":["dcurl"],"text/vnd.curl.mcurl":["mcurl"],"text/vnd.curl.scurl":["scurl"],"text/vnd.dvb.subtitle":["sub"],"text/vnd.fly":["fly"],"text/vnd.fmi.flexstor":["flx"],"text/vnd.graphviz":["gv"],"text/vnd.in3d.3dml":["3dml"],"text/vnd.in3d.spot":["spot"],"text/vnd.sun.j2me.app-descriptor":["jad"],"text/vnd.wap.wml":["wml"],"text/vnd.wap.wmlscript":["wmls"],"text/vtt":["vtt"],"text/x-asm":["s","asm"],"text/x-c":["c","cc","cxx","cpp","h","hh","dic"],"text/x-component":["htc"],"text/x-fortran":["f","for","f77","f90"],"text/x-handlebars-template":["hbs"],"text/x-java-source":["java"],"text/x-lua":["lua"],"text/x-markdown":["mkd"],"text/x-nfo":["nfo"],"text/x-opml":["opml"],"text/x-org":[],"text/x-pascal":["p","pas"],"text/x-processing":["pde"],"text/x-sass":["sass"],"text/x-scss":["scss"],"text/x-setext":["etx"],"text/x-sfv":["sfv"],"text/x-suse-ymp":["ymp"],"text/x-uuencode":["uu"],"text/x-vcalendar":["vcs"],"text/x-vcard":["vcf"],"text/xml":[],"text/yaml":["yaml","yml"],"video/3gpp":["3gp","3gpp"],"video/3gpp2":["3g2"],"video/h261":["h261"],"video/h263":["h263"],"video/h264":["h264"],"video/jpeg":["jpgv"],"video/jpm":["jpgm"],"video/mj2":["mj2","mjp2"],"video/mp2t":["ts"],"video/mp4":["mp4","mp4v","mpg4"],"video/mpeg":["mpeg","mpg","mpe","m1v","m2v"],"video/ogg":["ogv"],"video/quicktime":["qt","mov"],"video/vnd.dece.hd":["uvh","uvvh"],"video/vnd.dece.mobile":["uvm","uvvm"],"video/vnd.dece.pd":["uvp","uvvp"],"video/vnd.dece.sd":["uvs","uvvs"],"video/vnd.dece.video":["uvv","uvvv"],"video/vnd.dvb.file":["dvb"],"video/vnd.fvt":["fvt"],"video/vnd.mpegurl":["mxu","m4u"],"video/vnd.ms-playready.media.pyv":["pyv"],"video/vnd.uvvu.mp4":["uvu","uvvu"],"video/vnd.vivo":["viv"],"video/webm":["webm"],"video/x-f4v":["f4v"],"video/x-fli":["fli"],"video/x-flv":["flv"],"video/x-m4v":["m4v"],"video/x-matroska":["mkv","mk3d","mks"],"video/x-mng":["mng"],"video/x-ms-asf":["asf","asx"],"video/x-ms-vob":["vob"],"video/x-ms-wm":["wm"],"video/x-ms-wmv":["wmv"],"video/x-ms-wmx":["wmx"],"video/x-ms-wvx":["wvx"],"video/x-msvideo":["avi"],"video/x-sgi-movie":["movie"],"video/x-smv":["smv"],"x-conference/x-cooltalk":["ice"]} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 12eb038..0000000 --- a/yarn.lock +++ /dev/null @@ -1,315 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@types/node@*": - version "13.13.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.5.tgz#96ec3b0afafd64a4ccea9107b75bf8489f0e5765" - integrity sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g== - -"@types/yauzl@^2.9.1": - version "2.9.1" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" - integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== - dependencies: - "@types/node" "*" - -agent-base@5: - version "5.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" - integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -bl@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" - integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -buffer@^5.2.1, buffer@^5.5.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -debug@4, debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -extract-zip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492" - integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= - dependencies: - pend "~1.2.0" - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -get-stream@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== - dependencies: - pump "^3.0.0" - -glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -https-proxy-agent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" - integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== - dependencies: - agent-base "5" - debug "4" - -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -mime@^2.0.3: - version "2.4.5" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.5.tgz#d8de2ecb92982dedbb6541c9b6841d7f218ea009" - integrity sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w== - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -mkdirp-classic@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - -ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= - -progress@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -proxy-from-env@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -puppeteer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-3.0.4.tgz#f445aae0a6732c65bbb90e963dcd6fd8fde0d780" - integrity sha512-1QEb4tJXXbNId7WSHlcDkS3B4GklTIebKn8Y9D6B7tAdUjQncb+8QlTjbQsAgGX5dhRG32Qycuk5XKzJgLs0sg== - dependencies: - debug "^4.1.0" - extract-zip "^2.0.0" - https-proxy-agent "^4.0.0" - mime "^2.0.3" - progress "^2.0.1" - proxy-from-env "^1.0.0" - rimraf "^3.0.2" - tar-fs "^2.0.0" - unbzip2-stream "^1.3.3" - ws "^7.2.3" - -readable-stream@^3.1.1, readable-stream@^3.4.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -safe-buffer@~5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -tar-fs@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" - integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.0.0" - -tar-stream@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" - integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q== - dependencies: - bl "^4.0.1" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -through@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -unbzip2-stream@^1.3.3: - version "1.4.2" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.2.tgz#84eb9e783b186d8fb397515fbb656f312f1a7dbf" - integrity sha512-pZMVAofMrrHX6Ik39hCk470kulCbmZ2SWfQLPmTWqfJV/oUm0gn1CblvHdUu4+54Je6Jq34x8kY6XjTy6dMkOg== - dependencies: - buffer "^5.2.1" - through "^2.3.8" - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -ws@^7.2.3: - version "7.3.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd" - integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w== - -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" From e125154e6897d25f3fb3a51bbcae83f05e8625ad Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Wed, 13 May 2020 21:29:27 +0100 Subject: [PATCH 06/14] Implements directory listing for trailing forward slash --- config/directoryListing.js | 71 +++++++++++++++++++++++++++++++++++++ example/index.html | 1 + example/nested/component.js | 3 ++ example/nested/index.js | 3 ++ servor.js | 21 +++++++++-- 5 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 config/directoryListing.js create mode 100644 example/nested/component.js diff --git a/config/directoryListing.js b/config/directoryListing.js new file mode 100644 index 0000000..67154f4 --- /dev/null +++ b/config/directoryListing.js @@ -0,0 +1,71 @@ +const fs = require('fs'); +const path = require('path'); + +module.exports = (uri) => { + const dir = (x) => fs.statSync(path.join(uri, x)).isDirectory(); + const size = (x) => fs.statSync(path.join(uri, x)).size; + + const link = (x) => + dir(x) + ? ` +
+ 🗂 + ${x} + ${size(x)}B +
+ ` + : ` +
+ 📄 + ${x} + ${size(x)}B +
+ `; + + return ` + + + + + +
+ ${fs.readdirSync(uri).map(link).join('')} +
+ + + `; +}; diff --git a/example/index.html b/example/index.html index 19489db..e590055 100644 --- a/example/index.html +++ b/example/index.html @@ -4,6 +4,7 @@ SERVOR_TEST_INDEX + diff --git a/example/nested/component.js b/example/nested/component.js new file mode 100644 index 0000000..cc2f687 --- /dev/null +++ b/example/nested/component.js @@ -0,0 +1,3 @@ +import { react, html, css } from 'https://unpkg.com/rplus'; + +export default 'component'; diff --git a/example/nested/index.js b/example/nested/index.js index 1b73106..b90d770 100644 --- a/example/nested/index.js +++ b/example/nested/index.js @@ -1,6 +1,9 @@ /* SERVOR_TEST_NESTED_MODULE_INDEX */ import { react, html, css } from 'https://unpkg.com/rplus'; +import component from './component.js'; + +console.log(component); const style = css` font-family: 'Roboto', sans-serif; diff --git a/servor.js b/servor.js index ad2de72..c607086 100644 --- a/servor.js +++ b/servor.js @@ -9,7 +9,8 @@ const net = require('net'); const zlib = require('zlib'); const cwd = process.cwd(); -const mime = require('./config/mimetypes.js'); +const mimeTypes = require('./config/mimeTypes.js'); +const directoryListing = require('./config/directoryListing.js'); const watch = process.platform !== 'linux' @@ -96,7 +97,7 @@ module.exports = async ({ encoding = 'utf8'; } res.writeHead(status, { - 'Content-Type': mime[ext] || 'application/octet-stream', + 'Content-Type': mimeTypes[ext] || 'application/octet-stream', }); res.write(file, encoding); res.end(); @@ -135,7 +136,7 @@ module.exports = async ({ const uri = routes ? path.join(root, pathname, fallback) : path.join(root, fallback); - if (!fs.existsSync(uri)) return sendError(res, 404); + if (!fs.existsSync(uri)) return serveDirectoryListing(res, pathname); fs.readFile(uri, 'binary', (err, file) => { if (err) return sendError(res, 500); const status = pathname === '/' || routes ? 200 : 301; @@ -147,6 +148,18 @@ module.exports = async ({ }); }; + // Respond to requests with a trailing slash + + const serveDirectoryListing = (res, pathname) => { + const uri = path.join(root, pathname); + const base = path.join('/', pathname, '/'); + const doc = ``; + if (!fs.existsSync(uri)) return sendError(res, 404); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.write(doc + directoryListing(uri) + livereload); + res.end(); + }; + // Start the server on the desired port server((req, res) => { @@ -154,6 +167,8 @@ module.exports = async ({ res.setHeader('Access-Control-Allow-Origin', '*'); if (reload && pathname === '/livereload') return serveReload(res); if (!isRouteRequest(pathname)) return serveStaticFile(res, pathname); + if (pathname !== '/' && pathname.endsWith('/')) + return serveDirectoryListing(res, pathname); return serveRoute(res, pathname); }).listen(parseInt(port, 10)); From c5122363dfffce0dd862dd1cdad165093090d8b1 Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Wed, 13 May 2020 22:49:23 +0100 Subject: [PATCH 07/14] Renames config to utils and creates common --- certify.sh | 6 +- servor.js | 94 ++++++++--------------- {config => utils}/ca.conf | 0 utils/common.js | 31 ++++++++ {config => utils}/directoryListing.js | 0 config/mimetypes.js => utils/mimeTypes.js | 4 +- {config => utils}/ssl.conf | 0 7 files changed, 71 insertions(+), 64 deletions(-) rename {config => utils}/ca.conf (100%) create mode 100644 utils/common.js rename {config => utils}/directoryListing.js (100%) rename config/mimetypes.js => utils/mimeTypes.js (99%) rename {config => utils}/ssl.conf (100%) diff --git a/certify.sh b/certify.sh index 836942a..e8ddb7a 100755 --- a/certify.sh +++ b/certify.sh @@ -19,7 +19,7 @@ fi # Generate Certificate Authority openssl genrsa -out "tmp/${name}CA.key" 2048 &>/dev/null -openssl req -x509 -config config/ca.conf -new -nodes -key "tmp/${name}CA.key" -sha256 -days 1825 -out "${name}CA.pem" &>/dev/null +openssl req -x509 -config utils/ca.conf -new -nodes -key "tmp/${name}CA.key" -sha256 -days 1825 -out "${name}CA.pem" &>/dev/null # This is the part that demands root privileges if [ "$EUID" -eq 0 ] ; then @@ -33,10 +33,10 @@ fi # Generate CA-signed Certificate openssl genrsa -out "${name}.key" 2048 &>/dev/null -openssl req -new -config config/ca.conf -key "${name}.key" -out "tmp/${name}.csr" &>/dev/null +openssl req -new -config utils/ca.conf -key "${name}.key" -out "tmp/${name}.csr" &>/dev/null # Generate SSL Certificate -openssl x509 -req -in "tmp/${name}.csr" -CA "${name}CA.pem" -CAkey "tmp/${name}CA.key" -CAcreateserial -out "${name}.crt" -days 1825 -sha256 -extfile config/ssl.conf &>/dev/null +openssl x509 -req -in "tmp/${name}.csr" -CA "${name}CA.pem" -CAkey "tmp/${name}CA.key" -CAcreateserial -out "${name}.crt" -days 1825 -sha256 -extfile utils/ssl.conf &>/dev/null # Cleanup files rm servorCA.pem servorCA.srl diff --git a/servor.js b/servor.js index c607086..e1abfc1 100644 --- a/servor.js +++ b/servor.js @@ -4,35 +4,12 @@ const path = require('path'); const http = require('http'); const http2 = require('http2'); const https = require('https'); -const os = require('os'); -const net = require('net'); const zlib = require('zlib'); -const cwd = process.cwd(); - -const mimeTypes = require('./config/mimeTypes.js'); -const directoryListing = require('./config/directoryListing.js'); - -const watch = - process.platform !== 'linux' - ? (x, cb) => fs.watch(x, { recursive: true }, cb) - : (x, cb) => { - if (fs.statSync(x).isDirectory()) { - fs.watch(x, cb); - fs.readdirSync(x).forEach((xx) => watch(`${x}/${xx}`, cb)); - } - }; - -const freePort = (port = 0) => - new Promise((ok, x) => { - const s = net.createServer(); - s.on('error', x); - s.listen(port, () => (a = s.address()) && s.close(() => ok(a.port))); - }); -const ips = Object.values(os.networkInterfaces()) - .reduce((every, i) => [...every, ...i], []) - .filter((i) => i.family === 'IPv4' && i.internal === false) - .map((i) => i.address); +const mimeTypes = require('./utils/mimeTypes.js'); +const directoryListing = require('./utils/directoryListing.js'); + +const { fileWatch, usePort, networkIps } = require('./utils/common.js'); module.exports = async ({ root = '.', @@ -47,18 +24,19 @@ module.exports = async ({ // Try start on specified port then fail or find a free port try { - port = await freePort(port || process.env.PORT || 8080); + port = await usePort(port || process.env.PORT || 8080); } catch (e) { if (port || process.env.PORT) { console.log('[ERR] The port you have specified is already in use!'); process.exit(); } - port = await freePort(); + port = await usePort(); } // Configure globals - root = root.startsWith('/') ? root : path.join(cwd, root); + root = root.startsWith('/') ? root : path.join(process.cwd(), root); + const reloadClients = []; const protocol = credentials ? 'https' : 'http'; const server = credentials @@ -69,14 +47,14 @@ module.exports = async ({ const livereload = reload ? ` - - ` + + ` : ''; // Server utility functions @@ -84,6 +62,9 @@ module.exports = async ({ const isRouteRequest = (pathname) => !~pathname.split('/').pop().indexOf('.'); const utf8 = (file) => Buffer.from(file, 'binary').toString('utf8'); + const baseDoc = (pathname = '', base = path.join('/', pathname, '/')) => + ``; + const sendError = (res, status) => { res.writeHead(status); res.write(`${status}`); @@ -92,13 +73,11 @@ module.exports = async ({ const sendFile = (res, status, file, ext, encoding = 'binary') => { if (['js', 'css', 'html', 'json', 'xml', 'svg'].includes(ext)) { - res.setHeader('Content-Encoding', 'gzip'); + res.setHeader('content-encoding', 'gzip'); file = zlib.gzipSync(utf8(file)); encoding = 'utf8'; } - res.writeHead(status, { - 'Content-Type': mimeTypes[ext] || 'application/octet-stream', - }); + res.writeHead(status, { 'content-type': mimeTypes(ext) }); res.write(file, encoding); res.end(); }; @@ -110,9 +89,9 @@ module.exports = async ({ const serveReload = (res) => { res.writeHead(200, { - Connection: 'keep-alive', - 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', + connection: 'keep-alive', + 'content-type': 'text/event-stream', + 'cache-control': 'no-cache', }); sendMessage(res, 'connected', 'ready'); setInterval(sendMessage, 60000, res, 'ping', 'waiting'); @@ -133,17 +112,16 @@ module.exports = async ({ // Respond to requests without a file extension const serveRoute = (res, pathname) => { - const uri = routes + const index = routes ? path.join(root, pathname, fallback) : path.join(root, fallback); - if (!fs.existsSync(uri)) return serveDirectoryListing(res, pathname); - fs.readFile(uri, 'binary', (err, file) => { + if (!fs.existsSync(index) || (pathname.endsWith('/') && pathname !== '/')) + return serveDirectoryListing(res, pathname); + fs.readFile(index, 'binary', (err, file) => { if (err) return sendError(res, 500); const status = pathname === '/' || routes ? 200 : 301; - const base = path.join('/', pathname, '/'); - const doc = ``; if (module) file = ``; - file = doc + file + inject + livereload; + file = baseDoc(pathname) + file + inject + livereload; sendFile(res, status, file, 'html'); }); }; @@ -152,30 +130,26 @@ module.exports = async ({ const serveDirectoryListing = (res, pathname) => { const uri = path.join(root, pathname); - const base = path.join('/', pathname, '/'); - const doc = ``; if (!fs.existsSync(uri)) return sendError(res, 404); res.writeHead(200, { 'Content-Type': 'text/html' }); - res.write(doc + directoryListing(uri) + livereload); + res.write(baseDoc(pathname) + directoryListing(uri) + livereload); res.end(); }; - // Start the server on the desired port + // Start the server and route requests server((req, res) => { const pathname = decodeURI(url.parse(req.url).pathname); - res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('access-control-allow-origin', '*'); if (reload && pathname === '/livereload') return serveReload(res); if (!isRouteRequest(pathname)) return serveStaticFile(res, pathname); - if (pathname !== '/' && pathname.endsWith('/')) - return serveDirectoryListing(res, pathname); return serveRoute(res, pathname); }).listen(parseInt(port, 10)); // Notify livereload reloadClients on file change reload && - watch(root, () => { + fileWatch(root, () => { while (reloadClients.length > 0) sendMessage(reloadClients.pop(), 'message', 'reload'); }); @@ -188,5 +162,5 @@ module.exports = async ({ }); const x = { url: `${protocol}://localhost:${port}` }; - return { ...x, root, protocol, port, ips }; + return { ...x, root, protocol, port, ips: networkIps }; }; diff --git a/config/ca.conf b/utils/ca.conf similarity index 100% rename from config/ca.conf rename to utils/ca.conf diff --git a/utils/common.js b/utils/common.js new file mode 100644 index 0000000..201abbe --- /dev/null +++ b/utils/common.js @@ -0,0 +1,31 @@ +const fs = require('fs'); +const os = require('os'); +const net = require('net'); + +const fileWatch = + process.platform !== 'linux' + ? (x, cb) => fs.watch(x, { recursive: true }, cb) + : (x, cb) => { + if (fs.statSync(x).isDirectory()) { + fs.watch(x, cb); + fs.readdirSync(x).forEach((xx) => fileWatch(`${x}/${xx}`, cb)); + } + }; + +module.exports.fileWatch = fileWatch; + +const usePort = (port = 0) => + new Promise((ok, x) => { + const s = net.createServer(); + s.on('error', x); + s.listen(port, () => (a = s.address()) && s.close(() => ok(a.port))); + }); + +module.exports.usePort = usePort; + +const networkIps = Object.values(os.networkInterfaces()) + .reduce((every, i) => [...every, ...i], []) + .filter((i) => i.family === 'IPv4' && i.internal === false) + .map((i) => i.address); + +module.exports.networkIps = networkIps; diff --git a/config/directoryListing.js b/utils/directoryListing.js similarity index 100% rename from config/directoryListing.js rename to utils/directoryListing.js diff --git a/config/mimetypes.js b/utils/mimeTypes.js similarity index 99% rename from config/mimetypes.js rename to utils/mimeTypes.js index 0ebeed8..03736db 100644 --- a/config/mimetypes.js +++ b/utils/mimeTypes.js @@ -899,8 +899,10 @@ const types = { 'x-conference/x-cooltalk': ['ice'], }; -module.exports = Object.entries(types).reduce( +const mimes = Object.entries(types).reduce( (all, [type, exts]) => Object.assign(all, ...exts.map((ext) => ({ [ext]: type }))), {} ); + +module.exports = (ext) => mimes[ext] || 'application/octet-stream'; diff --git a/config/ssl.conf b/utils/ssl.conf similarity index 100% rename from config/ssl.conf rename to utils/ssl.conf From c9739058b0602d73fd46ed7e8098db8e855f90b2 Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Thu, 14 May 2020 01:38:21 +0100 Subject: [PATCH 08/14] Move all test relates things into tests directory --- package.json | 10 +++++----- {example => tests/example}/assets/exists.png | Bin .../example}/assets/file with space.html | 0 {example => tests/example}/assets/index.css | 0 {example => tests/example}/assets/index.js | 0 {example => tests/example}/index.html | 0 {example => tests/example}/index.js | 0 {example => tests/example}/nested/assets/exists.png | Bin {example => tests/example}/nested/assets/index.css | 0 {example => tests/example}/nested/assets/index.js | 0 {example => tests/example}/nested/component.js | 0 {example => tests/example}/nested/index.html | 0 {example => tests/example}/nested/index.js | 0 test.js => tests/index.js | 3 ++- 14 files changed, 7 insertions(+), 6 deletions(-) rename {example => tests/example}/assets/exists.png (100%) rename {example => tests/example}/assets/file with space.html (100%) rename {example => tests/example}/assets/index.css (100%) rename {example => tests/example}/assets/index.js (100%) rename {example => tests/example}/index.html (100%) rename {example => tests/example}/index.js (100%) rename {example => tests/example}/nested/assets/exists.png (100%) rename {example => tests/example}/nested/assets/index.css (100%) rename {example => tests/example}/nested/assets/index.js (100%) rename {example => tests/example}/nested/component.js (100%) rename {example => tests/example}/nested/index.html (100%) rename {example => tests/example}/nested/index.js (100%) rename test.js => tests/index.js (97%) diff --git a/package.json b/package.json index d00e628..269c7a7 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,15 @@ "servor": "./cli.js" }, "keywords": [ - "development", "server", - "node", - "spa", - "reload" + "https", + "livereload", + "spa" ], "scripts": { + "start": "sudo node cli tests/example --reload --browse --secure --routes --module", "cleanup": "rm -f servor.key servor.crt", - "test": "npm run cleanup && node test.js" + "test": "npm run cleanup && cd tests && node index.js" }, "author": "Luke Jackson ", "license": "MIT", diff --git a/example/assets/exists.png b/tests/example/assets/exists.png similarity index 100% rename from example/assets/exists.png rename to tests/example/assets/exists.png diff --git a/example/assets/file with space.html b/tests/example/assets/file with space.html similarity index 100% rename from example/assets/file with space.html rename to tests/example/assets/file with space.html diff --git a/example/assets/index.css b/tests/example/assets/index.css similarity index 100% rename from example/assets/index.css rename to tests/example/assets/index.css diff --git a/example/assets/index.js b/tests/example/assets/index.js similarity index 100% rename from example/assets/index.js rename to tests/example/assets/index.js diff --git a/example/index.html b/tests/example/index.html similarity index 100% rename from example/index.html rename to tests/example/index.html diff --git a/example/index.js b/tests/example/index.js similarity index 100% rename from example/index.js rename to tests/example/index.js diff --git a/example/nested/assets/exists.png b/tests/example/nested/assets/exists.png similarity index 100% rename from example/nested/assets/exists.png rename to tests/example/nested/assets/exists.png diff --git a/example/nested/assets/index.css b/tests/example/nested/assets/index.css similarity index 100% rename from example/nested/assets/index.css rename to tests/example/nested/assets/index.css diff --git a/example/nested/assets/index.js b/tests/example/nested/assets/index.js similarity index 100% rename from example/nested/assets/index.js rename to tests/example/nested/assets/index.js diff --git a/example/nested/component.js b/tests/example/nested/component.js similarity index 100% rename from example/nested/component.js rename to tests/example/nested/component.js diff --git a/example/nested/index.html b/tests/example/nested/index.html similarity index 100% rename from example/nested/index.html rename to tests/example/nested/index.html diff --git a/example/nested/index.js b/tests/example/nested/index.js similarity index 100% rename from example/nested/index.js rename to tests/example/nested/index.js diff --git a/test.js b/tests/index.js similarity index 97% rename from test.js rename to tests/index.js index d181a9b..a9e9c27 100644 --- a/test.js +++ b/tests/index.js @@ -1,4 +1,5 @@ const fs = require('fs'); +const path = require('path'); const puppeteer = require('puppeteer'); const cp = require('child_process'); @@ -21,7 +22,7 @@ const test = (cmd) => (url) => async (expect) => { ); // Run the command and wait for the server to start - const [c, ...a] = ('node cli example ' + cmd).trim().split(' '); + const [c, ...a] = ('node ../cli example ' + cmd).trim().split(' '); const servor = cp.spawn(c, a); const { origin } = await new Promise((resolve) => servor.stdout.once('data', (out) => { From 3671ea0c0463480b1209c34859b1815828c43b8c Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Fri, 15 May 2020 01:39:09 +0100 Subject: [PATCH 09/14] Implements repo cloning and --editor flag --- cli.js | 24 ++++++++++++++++++++++++ servor.js | 4 +++- tests/new/app.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/new/app.js diff --git a/cli.js b/cli.js index dfba48e..0055aee 100755 --- a/cli.js +++ b/cli.js @@ -24,6 +24,30 @@ const open = const admin = process.getuid && process.getuid() === 0; let credentials; + if (args[0].startsWith('gh:')) { + const repo = args[0].replace('gh:', ''); + const dest = repo.split('/')[1]; + if (!fs.existsSync(dest)) { + try { + require('child_process').execSync( + `git clone https://github.com/${repo}` + ); + } catch (e) { + console.log('\n ⚠️ Could not clone from https://github.com/', repo); + process.exit(); + } + } + args[0] = dest; + } + + if (~process.argv.indexOf('--editor')) { + try { + require('child_process').execSync(`code ${args[0]}`); + } catch (e) { + console.log(`\n ⚠️ Could not open code editor for ${args[0]}`); + } + } + // Generate ssl certificates if (~process.argv.indexOf('--secure')) { diff --git a/servor.js b/servor.js index e1abfc1..9239aac 100644 --- a/servor.js +++ b/servor.js @@ -15,11 +15,11 @@ module.exports = async ({ root = '.', module = false, fallback = module ? 'index.js' : 'index.html', - port, reload = true, routes = false, inject = '', credentials, + port, } = {}) => { // Try start on specified port then fail or find a free port @@ -87,6 +87,8 @@ module.exports = async ({ res.write('\n\n'); }; + // Respond to reload requests with keep alive + const serveReload = (res) => { res.writeHead(200, { connection: 'keep-alive', diff --git a/tests/new/app.js b/tests/new/app.js new file mode 100644 index 0000000..010a689 --- /dev/null +++ b/tests/new/app.js @@ -0,0 +1,30 @@ +// Simple react app by @lukejacksonn +// ---------------- + +import { react, html, css } from 'https://unpkg.com/rplus'; + +const random = () => `${Math.random() * 80}%`; +const style = css` + position: absolute; + font-size: 2rem; + width: 5rem; + height: 5rem; + border-radius: 50%; + border: 5px solid black; + font-weight: bold; +`; + +const app = () => { + const [score, setScore] = react.useState(0); + return html` + + `; +}; + +react.render(html`<${app} />`, document.body); From 31ed3dd3b5c6ce729dae82bc0b496f4b6b11d99b Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Fri, 15 May 2020 13:21:40 +0100 Subject: [PATCH 10/14] Ignore clone stdio and use applescript to openBrowser --- cli.js | 7 +-- utils/openBrowser.js | 29 +++++++++++++ utils/openChrome.applescript | 82 ++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 utils/openBrowser.js create mode 100644 utils/openChrome.applescript diff --git a/cli.js b/cli.js index 0055aee..237c2fd 100755 --- a/cli.js +++ b/cli.js @@ -1,6 +1,7 @@ #!/usr/bin/env node const fs = require('fs'); const servor = require('./servor.js'); +const openBrowser = require('./utils/openBrowser.js'); const readCredentials = () => ({ cert: fs.readFileSync(__dirname + '/servor.crt'), @@ -30,7 +31,8 @@ const open = if (!fs.existsSync(dest)) { try { require('child_process').execSync( - `git clone https://github.com/${repo}` + `git clone https://github.com/${repo}`, + { stdio: 'ignore' } ); } catch (e) { console.log('\n ⚠️ Could not clone from https://github.com/', repo); @@ -91,6 +93,5 @@ const open = // Browser the server index - !!~process.argv.indexOf('--browse') && - require('child_process').execSync(`${open} ${url}`); + !!~process.argv.indexOf('--browse') && openBrowser(url); })(); diff --git a/utils/openBrowser.js b/utils/openBrowser.js new file mode 100644 index 0000000..c820d34 --- /dev/null +++ b/utils/openBrowser.js @@ -0,0 +1,29 @@ +const childProcess = require('child_process'); + +module.exports = (url) => { + let cmd; + const args = []; + + if (process.platform === 'darwin') { + try { + childProcess.execSync( + `osascript openChrome.applescript "${encodeURI(url)}"`, + { + cwd: __dirname, + stdio: 'ignore', + } + ); + return true; + } catch (err) {} + cmd = 'open'; + } else if (process.platform === 'win32') { + cmd = 'cmd.exe'; + args.push('/c', 'start', '""', '/b'); + url = url.replace(/&/g, '^&'); + } else { + cmd = 'xdg-open'; + } + + args.push(url); + childProcess.spawn(cmd, args); +}; diff --git a/utils/openChrome.applescript b/utils/openChrome.applescript new file mode 100644 index 0000000..33c5ff7 --- /dev/null +++ b/utils/openChrome.applescript @@ -0,0 +1,82 @@ +(* +Copyright (c) 2015-present, Facebook, Inc. +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*) + +property targetTab: null +property targetTabIndex: -1 +property targetWindow: null + +on run argv + set theURL to item 1 of argv + + tell application "Chrome" + + if (count every window) = 0 then + make new window + end if + + -- 1: Looking for tab running debugger + -- then, Reload debugging tab if found + -- then return + set found to my lookupTabWithUrl(theURL) + if found then + set targetWindow's active tab index to targetTabIndex + tell targetTab to reload + tell targetWindow to activate + set index of targetWindow to 1 + return + end if + + -- 2: Looking for Empty tab + -- In case debugging tab was not found + -- We try to find an empty tab instead + set found to my lookupTabWithUrl("chrome://newtab/") + if found then + set targetWindow's active tab index to targetTabIndex + set URL of targetTab to theURL + tell targetWindow to activate + return + end if + + -- 3: Create new tab + -- both debugging and empty tab were not found + -- make a new tab with url + tell window 1 + activate + make new tab with properties {URL:theURL} + end tell + end tell +end run + +-- Function: +-- Lookup tab with given url +-- if found, store tab, index, and window in properties +-- (properties were declared on top of file) +on lookupTabWithUrl(lookupUrl) + tell application "Chrome" + -- Find a tab with the given url + set found to false + set theTabIndex to -1 + repeat with theWindow in every window + set theTabIndex to 0 + repeat with theTab in every tab of theWindow + set theTabIndex to theTabIndex + 1 + if (theTab's URL as string) contains lookupUrl then + -- assign tab, tab index, and window to properties + set targetTab to theTab + set targetTabIndex to theTabIndex + set targetWindow to theWindow + set found to true + exit repeat + end if + end repeat + + if found then + exit repeat + end if + end repeat + end tell + return found +end lookupTabWithUrl \ No newline at end of file From 12add7b92c34c6ab61719685c072aa8dc0fcf50d Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Fri, 15 May 2020 15:34:30 +0100 Subject: [PATCH 11/14] Update README and make console errors more uniform --- README.md | 14 +++++++++++--- cli.js | 10 ++++++---- tests/example/index.js | 2 ++ tests/example/nested/index.js | 2 ++ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8926c18..2c07ebf 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Servør can be invoked via the command line or programmatically using the node A ## Features -The motivation here was to write a package from the ground up with no dependencies; using only native node and browser APIs to do a specific task with minimal code. +The motivation here was to write a package from the ground up with no dependencies; using only native, node and browser APIs to do some specific tasks with minimal code. - 🗂 Serves static content like scripts, styles, images from a given directory - ♻️ Reloads the browser when project files get added, removed or modified @@ -21,6 +21,8 @@ The motivation here was to write a package from the ground up with no dependenci - 🖥 Redirects all path requests to a single file for frontend routing - 📦 Accepts both HTML and JavaScript files as the root file for a directory - 🔎 Discovers freely available ports to start on by default +- 📄 Renders directory listing for urls ending with a trailing slash +- 🗃 Opens browser tab and code editor to streamline quick start ## CLI Usage @@ -30,6 +32,8 @@ Run as a terminal command without adding it as a dependency using `npx`: npx servor ``` +> You can pass a GitHub repo as `` using the syntax `gh:/` + - `` path to serve static files from (defaults to current directory `.`) - `` the file served for all non-file requests (defaults to `index.html`) - `` what port you want to serve the files from (defaults to `8080`) @@ -41,6 +45,8 @@ Optional flags passed as non-positional arguments: - `--secure` starts the server with https using generated credentials - `--silent` prevents the server node process from logging to stdout - `--module` causes the server to wrap the root in script type module tags +- `--routes` causes the server to route nested index files if they exist +- `--editor` causes a code editor to be opened at the project root Example usage with npm scripts in a `package.json` file after running `npm i servor -D`: @@ -88,10 +94,12 @@ const servor = require('servor'); const instance = await servor({ root: '.', fallback: 'index.html', - port: 8080, + module: false, + routes: false, reload: false, inject: '' - credentials: {}, + credentials: null, + port: 8080, }); ``` diff --git a/cli.js b/cli.js index 237c2fd..b1800fa 100755 --- a/cli.js +++ b/cli.js @@ -25,7 +25,7 @@ const open = const admin = process.getuid && process.getuid() === 0; let credentials; - if (args[0].startsWith('gh:')) { + if (args[0] && args[0].startsWith('gh:')) { const repo = args[0].replace('gh:', ''); const dest = repo.split('/')[1]; if (!fs.existsSync(dest)) { @@ -35,7 +35,9 @@ const open = { stdio: 'ignore' } ); } catch (e) { - console.log('\n ⚠️ Could not clone from https://github.com/', repo); + console.log( + `\n ⚠️ Could not clone from https://github.com/${repo}\n` + ); process.exit(); } } @@ -46,7 +48,7 @@ const open = try { require('child_process').execSync(`code ${args[0]}`); } catch (e) { - console.log(`\n ⚠️ Could not open code editor for ${args[0]}`); + console.log(`\n ⚠️ Could not open code editor for ${args[0]}`); } } @@ -63,7 +65,7 @@ const open = credentials = readCredentials(); } catch (e) { console.log( - ' ⚠️ There was a problem generating ssl credentials. Try removing `--secure`' + '\n ⚠️ There was a problem generating ssl credentials. Try removing `--secure`\n' ); process.exit(); } diff --git a/tests/example/index.js b/tests/example/index.js index 3ad0c42..bdb9ea8 100644 --- a/tests/example/index.js +++ b/tests/example/index.js @@ -2,6 +2,8 @@ import { react, html, css } from 'https://unpkg.com/rplus'; +document.title = 'SERVOR_TEST_MODULE_INDEX'; + const style = css` font-family: 'Roboto', sans-serif; font-size: 16px; diff --git a/tests/example/nested/index.js b/tests/example/nested/index.js index b90d770..5fa928c 100644 --- a/tests/example/nested/index.js +++ b/tests/example/nested/index.js @@ -3,6 +3,8 @@ import { react, html, css } from 'https://unpkg.com/rplus'; import component from './component.js'; +document.title = 'SERVOR_TEST_NESTED_MODULE_INDEX'; + console.log(component); const style = css` From 6f65211265df8a4ce0a5ba82f66cf573bbb0004a Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Fri, 15 May 2020 16:03:09 +0100 Subject: [PATCH 12/14] Changes --routes to --static --- README.md | 4 ++-- cli.js | 2 +- package.json | 2 +- servor.js | 6 +++--- tests/index.js | 11 +++++------ 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2c07ebf..8391fa2 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Optional flags passed as non-positional arguments: - `--secure` starts the server with https using generated credentials - `--silent` prevents the server node process from logging to stdout - `--module` causes the server to wrap the root in script type module tags -- `--routes` causes the server to route nested index files if they exist +- `--static` causes the server to route nested index files if they exist - `--editor` causes a code editor to be opened at the project root Example usage with npm scripts in a `package.json` file after running `npm i servor -D`: @@ -95,7 +95,7 @@ const instance = await servor({ root: '.', fallback: 'index.html', module: false, - routes: false, + static: false, reload: false, inject: '' credentials: null, diff --git a/cli.js b/cli.js index b1800fa..0b33fc4 100755 --- a/cli.js +++ b/cli.js @@ -80,7 +80,7 @@ const open = port: args[2], reload: !!~process.argv.indexOf('--reload'), module: !!~process.argv.indexOf('--module'), - routes: !!~process.argv.indexOf('--routes'), + static: !!~process.argv.indexOf('--static'), credentials, }); diff --git a/package.json b/package.json index 269c7a7..ce7f64e 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "spa" ], "scripts": { - "start": "sudo node cli tests/example --reload --browse --secure --routes --module", + "start": "sudo node cli tests/example --reload --browse --secure --static --module", "cleanup": "rm -f servor.key servor.crt", "test": "npm run cleanup && cd tests && node index.js" }, diff --git a/servor.js b/servor.js index 9239aac..ab51e3e 100644 --- a/servor.js +++ b/servor.js @@ -16,7 +16,7 @@ module.exports = async ({ module = false, fallback = module ? 'index.js' : 'index.html', reload = true, - routes = false, + static = false, inject = '', credentials, port, @@ -114,14 +114,14 @@ module.exports = async ({ // Respond to requests without a file extension const serveRoute = (res, pathname) => { - const index = routes + const index = static ? path.join(root, pathname, fallback) : path.join(root, fallback); if (!fs.existsSync(index) || (pathname.endsWith('/') && pathname !== '/')) return serveDirectoryListing(res, pathname); fs.readFile(index, 'binary', (err, file) => { if (err) return sendError(res, 500); - const status = pathname === '/' || routes ? 200 : 301; + const status = pathname === '/' || static ? 200 : 301; if (module) file = ``; file = baseDoc(pathname) + file + inject + livereload; sendFile(res, status, file, 'html'); diff --git a/tests/index.js b/tests/index.js index a9e9c27..46e7f01 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1,5 +1,4 @@ const fs = require('fs'); -const path = require('path'); const puppeteer = require('puppeteer'); const cp = require('child_process'); @@ -131,17 +130,17 @@ const test = (cmd) => (url) => async (expect) => { includes: ['SERVOR_TEST_INDEX'], }); - await test('--routes')('/')({ + await test('--static')('/')({ ...base, includes: ['SERVOR_TEST_INDEX'], }); - await test('--routes')('/nested')({ + await test('--static')('/nested')({ ...base, includes: ['SERVOR_TEST_NESTED_INDEX'], }); - await test('--routes')('/broken-nested')({ + await test('--static')('/broken-nested')({ ...base, status: 404, gzip: false, @@ -157,13 +156,13 @@ const test = (cmd) => (url) => async (expect) => { includes: ['SERVOR_TEST_INDEX'], }); - await test('--secure --reload --routes --module')('/')({ + await test('--secure --reload --static --module')('/')({ ...base, reload: true, includes: ['SERVOR_TEST_MODULE_INDEX'], }); - await test('--secure --reload --routes --module')('/nested')({ + await test('--secure --reload --static --module')('/nested')({ ...base, reload: true, includes: ['SERVOR_TEST_NESTED_MODULE_INDEX'], From 0c7f2ae48d5d4be8638e074c2cf490f0b9a63931 Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Sat, 16 May 2020 20:13:40 +0100 Subject: [PATCH 13/14] More updates to README --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8391fa2..f62caa3 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,33 @@ > A dependency free dev server for modern web application development -The new and enhanced version of [http-server-spa](https://npmjs.com/http-server-spa). A very compact but capable static file server with https, live reloading, gzip and other useful features to support web app development on localhost and over a local network. +A very compact but capable static file server with https, live reloading, gzip and other useful features to support modern web app development on localhost and over a local network. The motivation here was to write a package from the ground up with no dependencies; using only native, node and browser APIs to do some specific tasks with minimal code. Servør can be invoked via the command line or programmatically using the node API. +**Quickstart Example** + +The following command instructs servør to; clone [perflink](https://github.com/lukejacksonn/perflink), start a server at the project root, open the url in a browser, open the source code in an editor and reload the browser when files change. + +```s +npx servor gh:lukejacksonn/perflink --browse --editor --reload +``` + +Most features are disabled by default but you can customize behaviour by passing positional arguments and flags to enable features. +
servor ## Features -The motivation here was to write a package from the ground up with no dependencies; using only native, node and browser APIs to do some specific tasks with minimal code. - - 🗂 Serves static content like scripts, styles, images from a given directory - ♻️ Reloads the browser when project files get added, removed or modified - 🗜 Uses gzip on common filetypes like html, css, js and json - 🔐 Supports https and http2 with trusted self signed certificates - 🖥 Redirects all path requests to a single file for frontend routing - 📦 Accepts both HTML and JavaScript files as the root file for a directory -- 🔎 Discovers freely available ports to start on by default +- 🔎 Discovers freely available ports to start if the default is in use - 📄 Renders directory listing for urls ending with a trailing slash - 🗃 Opens browser tab and code editor to streamline quick start @@ -46,14 +54,14 @@ Optional flags passed as non-positional arguments: - `--silent` prevents the server node process from logging to stdout - `--module` causes the server to wrap the root in script type module tags - `--static` causes the server to route nested index files if they exist -- `--editor` causes a code editor to be opened at the project root +- `--editor` opens a code editor (currently only vscode) at the project root Example usage with npm scripts in a `package.json` file after running `npm i servor -D`: ```json { "devDependencies": { - "servor": "3.1.0" + "servor": "4.0.0" }, "scripts": { "start": "servor www index.html 8080 --reload --browse" From f49aabb336b01c1b72d63765314777241509c1aa Mon Sep 17 00:00:00 2001 From: lukejacksonn Date: Mon, 18 May 2020 11:58:24 +0100 Subject: [PATCH 14/14] v4.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce7f64e..e8ba7e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "servor", - "version": "3.3.1", + "version": "4.0.0", "description": "A dependency free dev server for single page app development", "repository": "lukejacksonn/servor", "main": "./servor.js",