├── .gitignore
├── LICENSE
├── README.md
├── build-livereload.sh
├── package-lock.json
├── package.json
├── serve-http.d.ts
├── serve-http.js
├── serve-http.js.map
└── src
├── dirlist.js
├── fmt.js
├── livereload.js
├── main.js
├── mime.js
├── parseopts.js
├── safe.js
├── server.js
├── util.js
└── ws
├── LICENSE
├── async-limiter.js
├── buffer-util.js
├── constants.js
├── event-target.js
├── extension.js
├── index.js
├── permessage-deflate.js
├── receiver.js
├── sender.js
├── stream.js
├── validation.js
├── websocket-server.js
└── websocket.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.g
3 | *.g.map
4 | *.sublime*
5 | report.*
6 | /node_modules
7 | /_*
8 | /build
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019 Rasmus Andersson
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # serve-http
2 |
3 | Simple single-file local web server
4 |
5 | `npm i -g serve-http`
6 |
7 | - Single file without dependencies — copy it into your project.
8 | - Livereload — HTML pages reload automatically in web browsers as they change.
9 | - Safety feature: Only serves local connections unless given explicit command-line argument.
10 | - Safety feature: Refuses to serve directories outside home directory to remote connections.
11 |
12 | Install through [`npm i -g serve-http`](https://www.npmjs.com/package/serve-http) or by copying
13 | [`serve-http`](https://raw.githubusercontent.com/rsms/serve-http/master/serve-http)
14 | into your project (no dependencies; entire thing contained in a single small file.)
15 |
16 | ```
17 | $ ./serve-http -help
18 | Usage: serve-http [options] [
]
19 |
20 |
21 | Directory to serve files from. Defaults to the current directory.
22 |
23 | Options:
24 | -p, -port Listen on specific
25 | -host Bind to instead of "localhost"
26 | -public Accept connections from anywhere (same as -host "")
27 | -q, -quiet Don't log requests
28 | -no-livereload Disable livereload
29 | -no-dirlist Disable directory listing
30 | -dirlist-hidden Show files beginning with "." in directory listings
31 | -h, -help Show help and exit
32 | -version Print version to stdout and exit
33 |
34 | Examples:
35 |
36 | serve-http
37 | Serve current directory on some available port
38 |
39 | serve-http -p 8080 docs
40 | Serve directory ./docs locally on port 8080
41 |
42 | serve-http -public -no-dirlist
43 | Serve current directory publicly on some available port,
44 | without directory listing.
45 |
46 | ```
47 |
48 | ## JavaScript API
49 |
50 | serve-http can also be used as a library:
51 |
52 | ```js
53 | const { createServer } = require("serve-http")
54 | const server = createServer({ pubdir: __dirname, port: 1234 })
55 | // `server` is a standard nodejs http server instance.
56 | ```
57 |
58 | See TypeScript type definitions for documentation of the API:
59 | [`serve-http.d.ts`](serve-http.d.ts)
60 |
--------------------------------------------------------------------------------
/build-livereload.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | cd "$(dirname "$0")"
4 | export PATH=$PWD/node_modules/.bin:$PATH
5 | mkdir -p build
6 |
7 | SRCFILE=node_modules/livereload-js/dist/livereload.js
8 | TMPFILE=build/livereload.min.js
9 | OUTFILE=build/livereload-script.js
10 |
11 | if [ $OUTFILE -nt "$SRCFILE" ]; then
12 | echo "$OUTFILE up to date"
13 | exit
14 | fi
15 |
16 | echo "$SRCFILE -> $TMPFILE"
17 | esbuild \
18 | --minify \
19 | --platform=browser \
20 | --target=es2017 \
21 | --outfile="$TMPFILE" \
22 | "$SRCFILE"
23 |
24 | echo "$TMPFILE -> $OUTFILE"
25 | node <= 8"
33 | }
34 | },
35 | "node_modules/binary-extensions": {
36 | "version": "2.2.0",
37 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
38 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
39 | "dev": true,
40 | "engines": {
41 | "node": ">=8"
42 | }
43 | },
44 | "node_modules/braces": {
45 | "version": "3.0.2",
46 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
47 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
48 | "dev": true,
49 | "dependencies": {
50 | "fill-range": "^7.0.1"
51 | },
52 | "engines": {
53 | "node": ">=8"
54 | }
55 | },
56 | "node_modules/buffer-from": {
57 | "version": "1.1.2",
58 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
59 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
60 | "optional": true
61 | },
62 | "node_modules/chokidar": {
63 | "version": "3.5.3",
64 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
65 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
66 | "dev": true,
67 | "funding": [
68 | {
69 | "type": "individual",
70 | "url": "https://paulmillr.com/funding/"
71 | }
72 | ],
73 | "dependencies": {
74 | "anymatch": "~3.1.2",
75 | "braces": "~3.0.2",
76 | "glob-parent": "~5.1.2",
77 | "is-binary-path": "~2.1.0",
78 | "is-glob": "~4.0.1",
79 | "normalize-path": "~3.0.0",
80 | "readdirp": "~3.6.0"
81 | },
82 | "engines": {
83 | "node": ">= 8.10.0"
84 | },
85 | "optionalDependencies": {
86 | "fsevents": "~2.3.2"
87 | }
88 | },
89 | "node_modules/esbuild": {
90 | "version": "0.14.49",
91 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.49.tgz",
92 | "integrity": "sha512-/TlVHhOaq7Yz8N1OJrjqM3Auzo5wjvHFLk+T8pIue+fhnhIMpfAzsG6PLVMbFveVxqD2WOp3QHei+52IMUNmCw==",
93 | "dev": true,
94 | "hasInstallScript": true,
95 | "bin": {
96 | "esbuild": "bin/esbuild"
97 | },
98 | "engines": {
99 | "node": ">=12"
100 | },
101 | "optionalDependencies": {
102 | "esbuild-android-64": "0.14.49",
103 | "esbuild-android-arm64": "0.14.49",
104 | "esbuild-darwin-64": "0.14.49",
105 | "esbuild-darwin-arm64": "0.14.49",
106 | "esbuild-freebsd-64": "0.14.49",
107 | "esbuild-freebsd-arm64": "0.14.49",
108 | "esbuild-linux-32": "0.14.49",
109 | "esbuild-linux-64": "0.14.49",
110 | "esbuild-linux-arm": "0.14.49",
111 | "esbuild-linux-arm64": "0.14.49",
112 | "esbuild-linux-mips64le": "0.14.49",
113 | "esbuild-linux-ppc64le": "0.14.49",
114 | "esbuild-linux-riscv64": "0.14.49",
115 | "esbuild-linux-s390x": "0.14.49",
116 | "esbuild-netbsd-64": "0.14.49",
117 | "esbuild-openbsd-64": "0.14.49",
118 | "esbuild-sunos-64": "0.14.49",
119 | "esbuild-windows-32": "0.14.49",
120 | "esbuild-windows-64": "0.14.49",
121 | "esbuild-windows-arm64": "0.14.49"
122 | }
123 | },
124 | "node_modules/esbuild-android-64": {
125 | "version": "0.14.49",
126 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.49.tgz",
127 | "integrity": "sha512-vYsdOTD+yi+kquhBiFWl3tyxnj2qZJsl4tAqwhT90ktUdnyTizgle7TjNx6Ar1bN7wcwWqZ9QInfdk2WVagSww==",
128 | "cpu": [
129 | "x64"
130 | ],
131 | "dev": true,
132 | "optional": true,
133 | "os": [
134 | "android"
135 | ],
136 | "engines": {
137 | "node": ">=12"
138 | }
139 | },
140 | "node_modules/esbuild-android-arm64": {
141 | "version": "0.14.49",
142 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.49.tgz",
143 | "integrity": "sha512-g2HGr/hjOXCgSsvQZ1nK4nW/ei8JUx04Li74qub9qWrStlysaVmadRyTVuW32FGIpLQyc5sUjjZopj49eGGM2g==",
144 | "cpu": [
145 | "arm64"
146 | ],
147 | "dev": true,
148 | "optional": true,
149 | "os": [
150 | "android"
151 | ],
152 | "engines": {
153 | "node": ">=12"
154 | }
155 | },
156 | "node_modules/esbuild-darwin-64": {
157 | "version": "0.14.49",
158 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.49.tgz",
159 | "integrity": "sha512-3rvqnBCtX9ywso5fCHixt2GBCUsogNp9DjGmvbBohh31Ces34BVzFltMSxJpacNki96+WIcX5s/vum+ckXiLYg==",
160 | "cpu": [
161 | "x64"
162 | ],
163 | "dev": true,
164 | "optional": true,
165 | "os": [
166 | "darwin"
167 | ],
168 | "engines": {
169 | "node": ">=12"
170 | }
171 | },
172 | "node_modules/esbuild-darwin-arm64": {
173 | "version": "0.14.49",
174 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.49.tgz",
175 | "integrity": "sha512-XMaqDxO846srnGlUSJnwbijV29MTKUATmOLyQSfswbK/2X5Uv28M9tTLUJcKKxzoo9lnkYPsx2o8EJcTYwCs/A==",
176 | "cpu": [
177 | "arm64"
178 | ],
179 | "dev": true,
180 | "optional": true,
181 | "os": [
182 | "darwin"
183 | ],
184 | "engines": {
185 | "node": ">=12"
186 | }
187 | },
188 | "node_modules/esbuild-freebsd-64": {
189 | "version": "0.14.49",
190 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.49.tgz",
191 | "integrity": "sha512-NJ5Q6AjV879mOHFri+5lZLTp5XsO2hQ+KSJYLbfY9DgCu8s6/Zl2prWXVANYTeCDLlrIlNNYw8y34xqyLDKOmQ==",
192 | "cpu": [
193 | "x64"
194 | ],
195 | "dev": true,
196 | "optional": true,
197 | "os": [
198 | "freebsd"
199 | ],
200 | "engines": {
201 | "node": ">=12"
202 | }
203 | },
204 | "node_modules/esbuild-freebsd-arm64": {
205 | "version": "0.14.49",
206 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.49.tgz",
207 | "integrity": "sha512-lFLtgXnAc3eXYqj5koPlBZvEbBSOSUbWO3gyY/0+4lBdRqELyz4bAuamHvmvHW5swJYL7kngzIZw6kdu25KGOA==",
208 | "cpu": [
209 | "arm64"
210 | ],
211 | "dev": true,
212 | "optional": true,
213 | "os": [
214 | "freebsd"
215 | ],
216 | "engines": {
217 | "node": ">=12"
218 | }
219 | },
220 | "node_modules/esbuild-linux-32": {
221 | "version": "0.14.49",
222 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.49.tgz",
223 | "integrity": "sha512-zTTH4gr2Kb8u4QcOpTDVn7Z8q7QEIvFl/+vHrI3cF6XOJS7iEI1FWslTo3uofB2+mn6sIJEQD9PrNZKoAAMDiA==",
224 | "cpu": [
225 | "ia32"
226 | ],
227 | "dev": true,
228 | "optional": true,
229 | "os": [
230 | "linux"
231 | ],
232 | "engines": {
233 | "node": ">=12"
234 | }
235 | },
236 | "node_modules/esbuild-linux-64": {
237 | "version": "0.14.49",
238 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.49.tgz",
239 | "integrity": "sha512-hYmzRIDzFfLrB5c1SknkxzM8LdEUOusp6M2TnuQZJLRtxTgyPnZZVtyMeCLki0wKgYPXkFsAVhi8vzo2mBNeTg==",
240 | "cpu": [
241 | "x64"
242 | ],
243 | "dev": true,
244 | "optional": true,
245 | "os": [
246 | "linux"
247 | ],
248 | "engines": {
249 | "node": ">=12"
250 | }
251 | },
252 | "node_modules/esbuild-linux-arm": {
253 | "version": "0.14.49",
254 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.49.tgz",
255 | "integrity": "sha512-iE3e+ZVv1Qz1Sy0gifIsarJMQ89Rpm9mtLSRtG3AH0FPgAzQ5Z5oU6vYzhc/3gSPi2UxdCOfRhw2onXuFw/0lg==",
256 | "cpu": [
257 | "arm"
258 | ],
259 | "dev": true,
260 | "optional": true,
261 | "os": [
262 | "linux"
263 | ],
264 | "engines": {
265 | "node": ">=12"
266 | }
267 | },
268 | "node_modules/esbuild-linux-arm64": {
269 | "version": "0.14.49",
270 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.49.tgz",
271 | "integrity": "sha512-KLQ+WpeuY+7bxukxLz5VgkAAVQxUv67Ft4DmHIPIW+2w3ObBPQhqNoeQUHxopoW/aiOn3m99NSmSV+bs4BSsdA==",
272 | "cpu": [
273 | "arm64"
274 | ],
275 | "dev": true,
276 | "optional": true,
277 | "os": [
278 | "linux"
279 | ],
280 | "engines": {
281 | "node": ">=12"
282 | }
283 | },
284 | "node_modules/esbuild-linux-mips64le": {
285 | "version": "0.14.49",
286 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.49.tgz",
287 | "integrity": "sha512-n+rGODfm8RSum5pFIqFQVQpYBw+AztL8s6o9kfx7tjfK0yIGF6tm5HlG6aRjodiiKkH2xAiIM+U4xtQVZYU4rA==",
288 | "cpu": [
289 | "mips64el"
290 | ],
291 | "dev": true,
292 | "optional": true,
293 | "os": [
294 | "linux"
295 | ],
296 | "engines": {
297 | "node": ">=12"
298 | }
299 | },
300 | "node_modules/esbuild-linux-ppc64le": {
301 | "version": "0.14.49",
302 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.49.tgz",
303 | "integrity": "sha512-WP9zR4HX6iCBmMFH+XHHng2LmdoIeUmBpL4aL2TR8ruzXyT4dWrJ5BSbT8iNo6THN8lod6GOmYDLq/dgZLalGw==",
304 | "cpu": [
305 | "ppc64"
306 | ],
307 | "dev": true,
308 | "optional": true,
309 | "os": [
310 | "linux"
311 | ],
312 | "engines": {
313 | "node": ">=12"
314 | }
315 | },
316 | "node_modules/esbuild-linux-riscv64": {
317 | "version": "0.14.49",
318 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.49.tgz",
319 | "integrity": "sha512-h66ORBz+Dg+1KgLvzTVQEA1LX4XBd1SK0Fgbhhw4akpG/YkN8pS6OzYI/7SGENiN6ao5hETRDSkVcvU9NRtkMQ==",
320 | "cpu": [
321 | "riscv64"
322 | ],
323 | "dev": true,
324 | "optional": true,
325 | "os": [
326 | "linux"
327 | ],
328 | "engines": {
329 | "node": ">=12"
330 | }
331 | },
332 | "node_modules/esbuild-linux-s390x": {
333 | "version": "0.14.49",
334 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.49.tgz",
335 | "integrity": "sha512-DhrUoFVWD+XmKO1y7e4kNCqQHPs6twz6VV6Uezl/XHYGzM60rBewBF5jlZjG0nCk5W/Xy6y1xWeopkrhFFM0sQ==",
336 | "cpu": [
337 | "s390x"
338 | ],
339 | "dev": true,
340 | "optional": true,
341 | "os": [
342 | "linux"
343 | ],
344 | "engines": {
345 | "node": ">=12"
346 | }
347 | },
348 | "node_modules/esbuild-netbsd-64": {
349 | "version": "0.14.49",
350 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.49.tgz",
351 | "integrity": "sha512-BXaUwFOfCy2T+hABtiPUIpWjAeWK9P8O41gR4Pg73hpzoygVGnj0nI3YK4SJhe52ELgtdgWP/ckIkbn2XaTxjQ==",
352 | "cpu": [
353 | "x64"
354 | ],
355 | "dev": true,
356 | "optional": true,
357 | "os": [
358 | "netbsd"
359 | ],
360 | "engines": {
361 | "node": ">=12"
362 | }
363 | },
364 | "node_modules/esbuild-openbsd-64": {
365 | "version": "0.14.49",
366 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.49.tgz",
367 | "integrity": "sha512-lP06UQeLDGmVPw9Rg437Btu6J9/BmyhdoefnQ4gDEJTtJvKtQaUcOQrhjTq455ouZN4EHFH1h28WOJVANK41kA==",
368 | "cpu": [
369 | "x64"
370 | ],
371 | "dev": true,
372 | "optional": true,
373 | "os": [
374 | "openbsd"
375 | ],
376 | "engines": {
377 | "node": ">=12"
378 | }
379 | },
380 | "node_modules/esbuild-sunos-64": {
381 | "version": "0.14.49",
382 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.49.tgz",
383 | "integrity": "sha512-4c8Zowp+V3zIWje329BeLbGh6XI9c/rqARNaj5yPHdC61pHI9UNdDxT3rePPJeWcEZVKjkiAS6AP6kiITp7FSw==",
384 | "cpu": [
385 | "x64"
386 | ],
387 | "dev": true,
388 | "optional": true,
389 | "os": [
390 | "sunos"
391 | ],
392 | "engines": {
393 | "node": ">=12"
394 | }
395 | },
396 | "node_modules/esbuild-windows-32": {
397 | "version": "0.14.49",
398 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.49.tgz",
399 | "integrity": "sha512-q7Rb+J9yHTeKr9QTPDYkqfkEj8/kcKz9lOabDuvEXpXuIcosWCJgo5Z7h/L4r7rbtTH4a8U2FGKb6s1eeOHmJA==",
400 | "cpu": [
401 | "ia32"
402 | ],
403 | "dev": true,
404 | "optional": true,
405 | "os": [
406 | "win32"
407 | ],
408 | "engines": {
409 | "node": ">=12"
410 | }
411 | },
412 | "node_modules/esbuild-windows-64": {
413 | "version": "0.14.49",
414 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.49.tgz",
415 | "integrity": "sha512-+Cme7Ongv0UIUTniPqfTX6mJ8Deo7VXw9xN0yJEN1lQMHDppTNmKwAM3oGbD/Vqff+07K2gN0WfNkMohmG+dVw==",
416 | "cpu": [
417 | "x64"
418 | ],
419 | "dev": true,
420 | "optional": true,
421 | "os": [
422 | "win32"
423 | ],
424 | "engines": {
425 | "node": ">=12"
426 | }
427 | },
428 | "node_modules/esbuild-windows-arm64": {
429 | "version": "0.14.49",
430 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.49.tgz",
431 | "integrity": "sha512-v+HYNAXzuANrCbbLFJ5nmO3m5y2PGZWLe3uloAkLt87aXiO2mZr3BTmacZdjwNkNEHuH3bNtN8cak+mzVjVPfA==",
432 | "cpu": [
433 | "arm64"
434 | ],
435 | "dev": true,
436 | "optional": true,
437 | "os": [
438 | "win32"
439 | ],
440 | "engines": {
441 | "node": ">=12"
442 | }
443 | },
444 | "node_modules/fill-range": {
445 | "version": "7.0.1",
446 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
447 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
448 | "dev": true,
449 | "dependencies": {
450 | "to-regex-range": "^5.0.1"
451 | },
452 | "engines": {
453 | "node": ">=8"
454 | }
455 | },
456 | "node_modules/fsevents": {
457 | "version": "2.3.2",
458 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
459 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
460 | "dev": true,
461 | "hasInstallScript": true,
462 | "optional": true,
463 | "os": [
464 | "darwin"
465 | ],
466 | "engines": {
467 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
468 | }
469 | },
470 | "node_modules/glob-parent": {
471 | "version": "5.1.2",
472 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
473 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
474 | "dev": true,
475 | "dependencies": {
476 | "is-glob": "^4.0.1"
477 | },
478 | "engines": {
479 | "node": ">= 6"
480 | }
481 | },
482 | "node_modules/is-binary-path": {
483 | "version": "2.1.0",
484 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
485 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
486 | "dev": true,
487 | "dependencies": {
488 | "binary-extensions": "^2.0.0"
489 | },
490 | "engines": {
491 | "node": ">=8"
492 | }
493 | },
494 | "node_modules/is-extglob": {
495 | "version": "2.1.1",
496 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
497 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
498 | "dev": true,
499 | "engines": {
500 | "node": ">=0.10.0"
501 | }
502 | },
503 | "node_modules/is-glob": {
504 | "version": "4.0.3",
505 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
506 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
507 | "dev": true,
508 | "dependencies": {
509 | "is-extglob": "^2.1.1"
510 | },
511 | "engines": {
512 | "node": ">=0.10.0"
513 | }
514 | },
515 | "node_modules/is-number": {
516 | "version": "7.0.0",
517 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
518 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
519 | "dev": true,
520 | "engines": {
521 | "node": ">=0.12.0"
522 | }
523 | },
524 | "node_modules/livereload": {
525 | "version": "0.9.3",
526 | "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz",
527 | "integrity": "sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==",
528 | "dev": true,
529 | "dependencies": {
530 | "chokidar": "^3.5.0",
531 | "livereload-js": "^3.3.1",
532 | "opts": ">= 1.2.0",
533 | "ws": "^7.4.3"
534 | },
535 | "bin": {
536 | "livereload": "bin/livereload.js"
537 | },
538 | "engines": {
539 | "node": ">=8.0.0"
540 | }
541 | },
542 | "node_modules/livereload-js": {
543 | "version": "3.4.0",
544 | "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.4.0.tgz",
545 | "integrity": "sha512-F/pz9ZZP+R+arY94cECTZco7PXgBXyL+KVWUPZq8AQE9TOu14GV6fYeKOviv02JCvFa4Oi3Rs1hYEpfeajc+ow==",
546 | "dev": true
547 | },
548 | "node_modules/normalize-path": {
549 | "version": "3.0.0",
550 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
551 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
552 | "dev": true,
553 | "engines": {
554 | "node": ">=0.10.0"
555 | }
556 | },
557 | "node_modules/opts": {
558 | "version": "1.2.7",
559 | "resolved": "https://registry.npmjs.org/opts/-/opts-1.2.7.tgz",
560 | "integrity": "sha512-hwZhzGGG/GQ7igxAVFOEun2N4fWul31qE9nfBdCnZGQCB5+L7tN9xZ+94B4aUpLOJx/of3zZs5XsuubayQYQjA==",
561 | "dev": true
562 | },
563 | "node_modules/picomatch": {
564 | "version": "2.3.1",
565 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
566 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
567 | "dev": true,
568 | "engines": {
569 | "node": ">=8.6"
570 | },
571 | "funding": {
572 | "url": "https://github.com/sponsors/jonschlinkert"
573 | }
574 | },
575 | "node_modules/readdirp": {
576 | "version": "3.6.0",
577 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
578 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
579 | "dev": true,
580 | "dependencies": {
581 | "picomatch": "^2.2.1"
582 | },
583 | "engines": {
584 | "node": ">=8.10.0"
585 | }
586 | },
587 | "node_modules/source-map": {
588 | "version": "0.6.1",
589 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
590 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
591 | "optional": true,
592 | "engines": {
593 | "node": ">=0.10.0"
594 | }
595 | },
596 | "node_modules/source-map-support": {
597 | "version": "0.5.21",
598 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
599 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
600 | "optional": true,
601 | "dependencies": {
602 | "buffer-from": "^1.0.0",
603 | "source-map": "^0.6.0"
604 | }
605 | },
606 | "node_modules/to-regex-range": {
607 | "version": "5.0.1",
608 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
609 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
610 | "dev": true,
611 | "dependencies": {
612 | "is-number": "^7.0.0"
613 | },
614 | "engines": {
615 | "node": ">=8.0"
616 | }
617 | },
618 | "node_modules/ws": {
619 | "version": "7.5.9",
620 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
621 | "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
622 | "dev": true,
623 | "engines": {
624 | "node": ">=8.3.0"
625 | },
626 | "peerDependencies": {
627 | "bufferutil": "^4.0.1",
628 | "utf-8-validate": "^5.0.2"
629 | },
630 | "peerDependenciesMeta": {
631 | "bufferutil": {
632 | "optional": true
633 | },
634 | "utf-8-validate": {
635 | "optional": true
636 | }
637 | }
638 | }
639 | },
640 | "dependencies": {
641 | "anymatch": {
642 | "version": "3.1.2",
643 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
644 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
645 | "dev": true,
646 | "requires": {
647 | "normalize-path": "^3.0.0",
648 | "picomatch": "^2.0.4"
649 | }
650 | },
651 | "binary-extensions": {
652 | "version": "2.2.0",
653 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
654 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
655 | "dev": true
656 | },
657 | "braces": {
658 | "version": "3.0.2",
659 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
660 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
661 | "dev": true,
662 | "requires": {
663 | "fill-range": "^7.0.1"
664 | }
665 | },
666 | "buffer-from": {
667 | "version": "1.1.2",
668 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
669 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
670 | "optional": true
671 | },
672 | "chokidar": {
673 | "version": "3.5.3",
674 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
675 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
676 | "dev": true,
677 | "requires": {
678 | "anymatch": "~3.1.2",
679 | "braces": "~3.0.2",
680 | "fsevents": "~2.3.2",
681 | "glob-parent": "~5.1.2",
682 | "is-binary-path": "~2.1.0",
683 | "is-glob": "~4.0.1",
684 | "normalize-path": "~3.0.0",
685 | "readdirp": "~3.6.0"
686 | }
687 | },
688 | "esbuild": {
689 | "version": "0.14.49",
690 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.49.tgz",
691 | "integrity": "sha512-/TlVHhOaq7Yz8N1OJrjqM3Auzo5wjvHFLk+T8pIue+fhnhIMpfAzsG6PLVMbFveVxqD2WOp3QHei+52IMUNmCw==",
692 | "dev": true,
693 | "requires": {
694 | "esbuild-android-64": "0.14.49",
695 | "esbuild-android-arm64": "0.14.49",
696 | "esbuild-darwin-64": "0.14.49",
697 | "esbuild-darwin-arm64": "0.14.49",
698 | "esbuild-freebsd-64": "0.14.49",
699 | "esbuild-freebsd-arm64": "0.14.49",
700 | "esbuild-linux-32": "0.14.49",
701 | "esbuild-linux-64": "0.14.49",
702 | "esbuild-linux-arm": "0.14.49",
703 | "esbuild-linux-arm64": "0.14.49",
704 | "esbuild-linux-mips64le": "0.14.49",
705 | "esbuild-linux-ppc64le": "0.14.49",
706 | "esbuild-linux-riscv64": "0.14.49",
707 | "esbuild-linux-s390x": "0.14.49",
708 | "esbuild-netbsd-64": "0.14.49",
709 | "esbuild-openbsd-64": "0.14.49",
710 | "esbuild-sunos-64": "0.14.49",
711 | "esbuild-windows-32": "0.14.49",
712 | "esbuild-windows-64": "0.14.49",
713 | "esbuild-windows-arm64": "0.14.49"
714 | }
715 | },
716 | "esbuild-android-64": {
717 | "version": "0.14.49",
718 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.49.tgz",
719 | "integrity": "sha512-vYsdOTD+yi+kquhBiFWl3tyxnj2qZJsl4tAqwhT90ktUdnyTizgle7TjNx6Ar1bN7wcwWqZ9QInfdk2WVagSww==",
720 | "dev": true,
721 | "optional": true
722 | },
723 | "esbuild-android-arm64": {
724 | "version": "0.14.49",
725 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.49.tgz",
726 | "integrity": "sha512-g2HGr/hjOXCgSsvQZ1nK4nW/ei8JUx04Li74qub9qWrStlysaVmadRyTVuW32FGIpLQyc5sUjjZopj49eGGM2g==",
727 | "dev": true,
728 | "optional": true
729 | },
730 | "esbuild-darwin-64": {
731 | "version": "0.14.49",
732 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.49.tgz",
733 | "integrity": "sha512-3rvqnBCtX9ywso5fCHixt2GBCUsogNp9DjGmvbBohh31Ces34BVzFltMSxJpacNki96+WIcX5s/vum+ckXiLYg==",
734 | "dev": true,
735 | "optional": true
736 | },
737 | "esbuild-darwin-arm64": {
738 | "version": "0.14.49",
739 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.49.tgz",
740 | "integrity": "sha512-XMaqDxO846srnGlUSJnwbijV29MTKUATmOLyQSfswbK/2X5Uv28M9tTLUJcKKxzoo9lnkYPsx2o8EJcTYwCs/A==",
741 | "dev": true,
742 | "optional": true
743 | },
744 | "esbuild-freebsd-64": {
745 | "version": "0.14.49",
746 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.49.tgz",
747 | "integrity": "sha512-NJ5Q6AjV879mOHFri+5lZLTp5XsO2hQ+KSJYLbfY9DgCu8s6/Zl2prWXVANYTeCDLlrIlNNYw8y34xqyLDKOmQ==",
748 | "dev": true,
749 | "optional": true
750 | },
751 | "esbuild-freebsd-arm64": {
752 | "version": "0.14.49",
753 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.49.tgz",
754 | "integrity": "sha512-lFLtgXnAc3eXYqj5koPlBZvEbBSOSUbWO3gyY/0+4lBdRqELyz4bAuamHvmvHW5swJYL7kngzIZw6kdu25KGOA==",
755 | "dev": true,
756 | "optional": true
757 | },
758 | "esbuild-linux-32": {
759 | "version": "0.14.49",
760 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.49.tgz",
761 | "integrity": "sha512-zTTH4gr2Kb8u4QcOpTDVn7Z8q7QEIvFl/+vHrI3cF6XOJS7iEI1FWslTo3uofB2+mn6sIJEQD9PrNZKoAAMDiA==",
762 | "dev": true,
763 | "optional": true
764 | },
765 | "esbuild-linux-64": {
766 | "version": "0.14.49",
767 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.49.tgz",
768 | "integrity": "sha512-hYmzRIDzFfLrB5c1SknkxzM8LdEUOusp6M2TnuQZJLRtxTgyPnZZVtyMeCLki0wKgYPXkFsAVhi8vzo2mBNeTg==",
769 | "dev": true,
770 | "optional": true
771 | },
772 | "esbuild-linux-arm": {
773 | "version": "0.14.49",
774 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.49.tgz",
775 | "integrity": "sha512-iE3e+ZVv1Qz1Sy0gifIsarJMQ89Rpm9mtLSRtG3AH0FPgAzQ5Z5oU6vYzhc/3gSPi2UxdCOfRhw2onXuFw/0lg==",
776 | "dev": true,
777 | "optional": true
778 | },
779 | "esbuild-linux-arm64": {
780 | "version": "0.14.49",
781 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.49.tgz",
782 | "integrity": "sha512-KLQ+WpeuY+7bxukxLz5VgkAAVQxUv67Ft4DmHIPIW+2w3ObBPQhqNoeQUHxopoW/aiOn3m99NSmSV+bs4BSsdA==",
783 | "dev": true,
784 | "optional": true
785 | },
786 | "esbuild-linux-mips64le": {
787 | "version": "0.14.49",
788 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.49.tgz",
789 | "integrity": "sha512-n+rGODfm8RSum5pFIqFQVQpYBw+AztL8s6o9kfx7tjfK0yIGF6tm5HlG6aRjodiiKkH2xAiIM+U4xtQVZYU4rA==",
790 | "dev": true,
791 | "optional": true
792 | },
793 | "esbuild-linux-ppc64le": {
794 | "version": "0.14.49",
795 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.49.tgz",
796 | "integrity": "sha512-WP9zR4HX6iCBmMFH+XHHng2LmdoIeUmBpL4aL2TR8ruzXyT4dWrJ5BSbT8iNo6THN8lod6GOmYDLq/dgZLalGw==",
797 | "dev": true,
798 | "optional": true
799 | },
800 | "esbuild-linux-riscv64": {
801 | "version": "0.14.49",
802 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.49.tgz",
803 | "integrity": "sha512-h66ORBz+Dg+1KgLvzTVQEA1LX4XBd1SK0Fgbhhw4akpG/YkN8pS6OzYI/7SGENiN6ao5hETRDSkVcvU9NRtkMQ==",
804 | "dev": true,
805 | "optional": true
806 | },
807 | "esbuild-linux-s390x": {
808 | "version": "0.14.49",
809 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.49.tgz",
810 | "integrity": "sha512-DhrUoFVWD+XmKO1y7e4kNCqQHPs6twz6VV6Uezl/XHYGzM60rBewBF5jlZjG0nCk5W/Xy6y1xWeopkrhFFM0sQ==",
811 | "dev": true,
812 | "optional": true
813 | },
814 | "esbuild-netbsd-64": {
815 | "version": "0.14.49",
816 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.49.tgz",
817 | "integrity": "sha512-BXaUwFOfCy2T+hABtiPUIpWjAeWK9P8O41gR4Pg73hpzoygVGnj0nI3YK4SJhe52ELgtdgWP/ckIkbn2XaTxjQ==",
818 | "dev": true,
819 | "optional": true
820 | },
821 | "esbuild-openbsd-64": {
822 | "version": "0.14.49",
823 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.49.tgz",
824 | "integrity": "sha512-lP06UQeLDGmVPw9Rg437Btu6J9/BmyhdoefnQ4gDEJTtJvKtQaUcOQrhjTq455ouZN4EHFH1h28WOJVANK41kA==",
825 | "dev": true,
826 | "optional": true
827 | },
828 | "esbuild-sunos-64": {
829 | "version": "0.14.49",
830 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.49.tgz",
831 | "integrity": "sha512-4c8Zowp+V3zIWje329BeLbGh6XI9c/rqARNaj5yPHdC61pHI9UNdDxT3rePPJeWcEZVKjkiAS6AP6kiITp7FSw==",
832 | "dev": true,
833 | "optional": true
834 | },
835 | "esbuild-windows-32": {
836 | "version": "0.14.49",
837 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.49.tgz",
838 | "integrity": "sha512-q7Rb+J9yHTeKr9QTPDYkqfkEj8/kcKz9lOabDuvEXpXuIcosWCJgo5Z7h/L4r7rbtTH4a8U2FGKb6s1eeOHmJA==",
839 | "dev": true,
840 | "optional": true
841 | },
842 | "esbuild-windows-64": {
843 | "version": "0.14.49",
844 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.49.tgz",
845 | "integrity": "sha512-+Cme7Ongv0UIUTniPqfTX6mJ8Deo7VXw9xN0yJEN1lQMHDppTNmKwAM3oGbD/Vqff+07K2gN0WfNkMohmG+dVw==",
846 | "dev": true,
847 | "optional": true
848 | },
849 | "esbuild-windows-arm64": {
850 | "version": "0.14.49",
851 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.49.tgz",
852 | "integrity": "sha512-v+HYNAXzuANrCbbLFJ5nmO3m5y2PGZWLe3uloAkLt87aXiO2mZr3BTmacZdjwNkNEHuH3bNtN8cak+mzVjVPfA==",
853 | "dev": true,
854 | "optional": true
855 | },
856 | "fill-range": {
857 | "version": "7.0.1",
858 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
859 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
860 | "dev": true,
861 | "requires": {
862 | "to-regex-range": "^5.0.1"
863 | }
864 | },
865 | "fsevents": {
866 | "version": "2.3.2",
867 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
868 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
869 | "dev": true,
870 | "optional": true
871 | },
872 | "glob-parent": {
873 | "version": "5.1.2",
874 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
875 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
876 | "dev": true,
877 | "requires": {
878 | "is-glob": "^4.0.1"
879 | }
880 | },
881 | "is-binary-path": {
882 | "version": "2.1.0",
883 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
884 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
885 | "dev": true,
886 | "requires": {
887 | "binary-extensions": "^2.0.0"
888 | }
889 | },
890 | "is-extglob": {
891 | "version": "2.1.1",
892 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
893 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
894 | "dev": true
895 | },
896 | "is-glob": {
897 | "version": "4.0.3",
898 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
899 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
900 | "dev": true,
901 | "requires": {
902 | "is-extglob": "^2.1.1"
903 | }
904 | },
905 | "is-number": {
906 | "version": "7.0.0",
907 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
908 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
909 | "dev": true
910 | },
911 | "livereload": {
912 | "version": "0.9.3",
913 | "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz",
914 | "integrity": "sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==",
915 | "dev": true,
916 | "requires": {
917 | "chokidar": "^3.5.0",
918 | "livereload-js": "^3.3.1",
919 | "opts": ">= 1.2.0",
920 | "ws": "^7.4.3"
921 | }
922 | },
923 | "livereload-js": {
924 | "version": "3.4.0",
925 | "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.4.0.tgz",
926 | "integrity": "sha512-F/pz9ZZP+R+arY94cECTZco7PXgBXyL+KVWUPZq8AQE9TOu14GV6fYeKOviv02JCvFa4Oi3Rs1hYEpfeajc+ow==",
927 | "dev": true
928 | },
929 | "normalize-path": {
930 | "version": "3.0.0",
931 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
932 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
933 | "dev": true
934 | },
935 | "opts": {
936 | "version": "1.2.7",
937 | "resolved": "https://registry.npmjs.org/opts/-/opts-1.2.7.tgz",
938 | "integrity": "sha512-hwZhzGGG/GQ7igxAVFOEun2N4fWul31qE9nfBdCnZGQCB5+L7tN9xZ+94B4aUpLOJx/of3zZs5XsuubayQYQjA==",
939 | "dev": true
940 | },
941 | "picomatch": {
942 | "version": "2.3.1",
943 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
944 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
945 | "dev": true
946 | },
947 | "readdirp": {
948 | "version": "3.6.0",
949 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
950 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
951 | "dev": true,
952 | "requires": {
953 | "picomatch": "^2.2.1"
954 | }
955 | },
956 | "source-map": {
957 | "version": "0.6.1",
958 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
959 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
960 | "optional": true
961 | },
962 | "source-map-support": {
963 | "version": "0.5.21",
964 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
965 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
966 | "optional": true,
967 | "requires": {
968 | "buffer-from": "^1.0.0",
969 | "source-map": "^0.6.0"
970 | }
971 | },
972 | "to-regex-range": {
973 | "version": "5.0.1",
974 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
975 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
976 | "dev": true,
977 | "requires": {
978 | "is-number": "^7.0.0"
979 | }
980 | },
981 | "ws": {
982 | "version": "7.5.9",
983 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
984 | "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
985 | "dev": true,
986 | "requires": {}
987 | }
988 | }
989 | }
990 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "serve-http",
3 | "version": "1.0.7",
4 | "description": "Simple single-file local web server with livereload",
5 | "bin": {
6 | "serve-http": "serve-http.js"
7 | },
8 | "main": "serve-http.js",
9 | "typings": "serve-http.d.ts",
10 | "files": [
11 | "LICENSE",
12 | "README.md",
13 | "serve-http.js",
14 | "serve-http.js.map",
15 | "serve-http.d.ts"
16 | ],
17 | "scripts": {
18 | "build_old": "./build.sh",
19 | "dev_old": "./build.sh -w",
20 | "build": "sh build-livereload.sh && esbuild --define:DEBUG=false --define:WITH_LIVERELOAD=true --define:VERSION=$(node -p 'JSON.stringify(require(\"./package.json\").version)') --minify --bundle --platform=node --target=node14 --sourcemap --sources-content=false --banner:js='#!/usr/bin/env node' --outfile=serve-http.js src/main.js && chmod +x serve-http.js",
21 | "build-dev": "sh build-livereload.sh && esbuild --define:DEBUG=true --define:WITH_LIVERELOAD=true --define:VERSION=$(node -p 'JSON.stringify(require(\"./package.json\").version)') --bundle --platform=node --target=node14 --sourcemap --sources-content=false --banner:js='#!/usr/bin/env node\ntry { require(\"source-map-support\").install() }catch(_){};' --outfile=serve-http.js src/main.js && chmod +x serve-http.js",
22 | "build-dev-watch": "sh build-livereload.sh && esbuild --watch --define:DEBUG=true --define:WITH_LIVERELOAD=true --define:VERSION=$(node -p 'JSON.stringify(require(\"./package.json\").version)') --bundle --platform=node --target=node14 --sourcemap --sources-content=false --banner:js='#!/usr/bin/env node\ntry { require(\"source-map-support\").install() }catch(_){};' --outfile=serve-http.js src/main.js && chmod +x serve-http.js"
23 | },
24 | "author": "Rasmus Andersson ",
25 | "homepage": "https://github.com/rsms/serve-http/",
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/rsms/serve-http.git"
29 | },
30 | "license": "MIT",
31 | "optionalDependencies": {
32 | "source-map-support": "^0.5.21"
33 | },
34 | "devDependencies": {
35 | "esbuild": "^0.14.49",
36 | "livereload": "^0.9.3"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/serve-http.d.ts:
--------------------------------------------------------------------------------
1 | import { Server } from "http"
2 |
3 | export interface ServerConfig {
4 | /// bind to port. If falsy, some free user-space port is assigned
5 | port? :number
6 |
7 | /// bind to host (defaults to "localhost")
8 | host? :string
9 |
10 | // bind to public address; make server accessible to the outside world
11 | public? :boolean
12 |
13 | /// directory to serve. (default "."; current directory.)
14 | pubdir? :string
15 |
16 | /// don't log requests (still logs errors)
17 | quiet? :boolean
18 |
19 | /// log "serving DIR at URL" message on startup
20 | showServingMessage? :boolean
21 |
22 | /// mime type for unknown file types
23 | defaultMimeType? :string
24 |
25 | /// file to serve for directory requests (defaults to "index.html")
26 | indexFilename? :string
27 |
28 | /// disable or customize directory listing
29 | dirlist? :DirlistOptions|boolean
30 |
31 | /// disable or customize livereload
32 | livereload? :LiveReloadOptions|boolean
33 | }
34 |
35 | export interface DirlistOptions {
36 | /// disable directory listing
37 | disable? :boolean
38 |
39 | /// include files which name starts with "."
40 | showHidden? :boolean
41 | }
42 |
43 | export interface LiveReloadOptions {
44 | /// disable livereload
45 | disable? :boolean
46 |
47 | /// livereload server bind port. (default based on server port)
48 | port? :number
49 | }
50 |
51 | /// Start a server
52 | export function createServer(config? :ServerConfig) :Server
53 |
--------------------------------------------------------------------------------
/src/dirlist.js:
--------------------------------------------------------------------------------
1 | import { dlog } from "./util"
2 |
3 | const Path = require("path")
4 | const fs = require("fs")
5 | const promisify = require("util").promisify
6 | const readdir = promisify(fs.readdir)
7 | const readlink = promisify(fs.readlink)
8 |
9 | const htmlEntities = {
10 | '&': '&',
11 | '<': '<',
12 | '>': '>',
13 | '"': '"',
14 | }
15 |
16 |
17 | function htmlescape(s) {
18 | return s.replace(/[&<>"']/g, m => htmlEntities[m])
19 | }
20 |
21 |
22 | // f(filename :string, pathname :string, opts :DirlistOptions) :Promise
23 | export async function createDirectoryListingHTML(filename, pathname, opts) {
24 | let ents = await readdir(filename, {withFileTypes:true, encoding:"utf8"})
25 | let files = []
26 | for (let f of ents) {
27 | let name = f.name
28 | if (!opts.showHidden && name[0] == ".") {
29 | continue
30 | }
31 |
32 | let extra = ""
33 | if (f.isDirectory()) {
34 | name += "/"
35 | } else if (f.isSymbolicLink()) {
36 | try {
37 | let target = await readlink(Path.join(filename, f.name))
38 | extra = ` → ${htmlescape(target)}`
39 | } catch (_) {}
40 | }
41 |
42 | files.push(`${htmlescape(name)}${extra}`)
43 | }
44 |
45 | if (pathname != '/') {
46 | files.unshift('..')
47 | }
48 |
49 | let title = []
50 | let pathComponents = pathname.split("/").filter(s => s.length > 0)
51 | let pardir = "/"
52 | for (let c of pathComponents) {
53 | let dir = pardir + c + "/"
54 | pardir = dir
55 | title.push(`${htmlescape(c)}`)
56 | }
57 | title = `/${title.join("/")}`
58 |
59 | return `
60 |
61 |
62 | ${htmlescape(pathname)}
63 |
70 |
71 | ${title}
72 |
73 | ${files.join("\n ")}
74 |
75 |
76 | \n`
77 | }
78 |
--------------------------------------------------------------------------------
/src/fmt.js:
--------------------------------------------------------------------------------
1 | // type Formatter = (v :any, n? :number) => any
2 | const inspect = require("util").inspect
3 |
4 | const formatters = {
5 | s: String,
6 | j: JSON.stringify,
7 | j_: (v, n) => JSON.stringify(v, null, n),
8 | r: inspect,
9 | r_: inspect,
10 | q: v => JSON.stringify(String(v)),
11 | n: Number,
12 | f: Number,
13 | f_: (v, n) => Number(v).toFixed(n),
14 | i: Math.round,
15 | d: Math.round,
16 | x: v => Math.round(v).toString(16),
17 | X: v => Math.round(v).toString(16).toUpperCase(),
18 | }
19 |
20 |
21 | // fmt formats a string
22 | //
23 | // Format specifiers:
24 | //
25 | // %s String(value)
26 | // %r inspect(value)
27 | // %Nr inspect(value, maxdepth=N)
28 | // %j JSON.stringify(value)
29 | // %jN JSON.stringify(value, null, N)
30 | // %q JSON.stringify(String(value))
31 | // %n, %f Number(value)
32 | // %fN Number(value).toFixed(N)
33 | // %i, %d Math.round(value)
34 | // %x Math.round(value).toString(16)
35 | // %X Math.round(value).toString(16).toUpperCase()
36 | // %% "%"
37 | //
38 | // A value that is a function is called and its return value is used.
39 | //
40 | // fmt(format :string, ...args :any[]) :string
41 | export function fmt(format, ...args) {
42 | let index = 0
43 | let s = format.replace(/%(?:([sjrqnfidxX%])|(\d+)([jrf]))/g, (s, ...m) => {
44 | let spec = m[0]
45 | if (spec == "%") {
46 | return "%"
47 | } else if (!spec) {
48 | // with leading number
49 | spec = m[2]
50 | }
51 | if (index == args.length) {
52 | throw new Error(`superfluous parameter %${spec} at offset ${m[3]}`)
53 | }
54 | let v = args[index++]
55 | if (typeof v == "function") {
56 | v = v()
57 | }
58 | return m[0] ? formatters[spec](v) : formatters[spec + "_"](v, parseInt(m[1]))
59 | })
60 | if (index < args.length) {
61 | // throw new Error(`superfluous arguments`)
62 | s += `(fmt:extra ${args.slice(index).map(v => `${typeof v}=${v}`).join(", ")})`
63 | }
64 | return s
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/src/livereload.js:
--------------------------------------------------------------------------------
1 | /*
2 | livereload by Joshua Peek, converted from CoffeeScript to JS ES6.
3 |
4 | livereload.coffee licensed as follows: (MIT)
5 | Copyright (c) 2010 Joshua Peek
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining
8 | a copy of this software and associated documentation files (the
9 | "Software"), to deal in the Software without restriction, including
10 | without limitation the rights to use, copy, modify, merge, publish,
11 | distribute, sublicense, and/or sell copies of the Software, and to
12 | permit persons to whom the Software is furnished to do so, subject to
13 | the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 | */
26 |
27 | const fs = require('fs')
28 | , path = require('path')
29 | , http = require('http')
30 | , https = require('https')
31 | , url = require('url')
32 | , EventEmitter = require('events')
33 |
34 | // import * as ws from '../node_modules/ws/index'
35 | // import { WebSocketServer } from '../node_modules/ws/lib/websocket-server'
36 | import { WebSocketServer } from './ws/websocket-server'
37 |
38 | let protocol_version = '7';
39 | let defaultPort = 35729;
40 | let defaultExts = [
41 | "html",
42 | "css",
43 | "js",
44 | "wasm",
45 | "png",
46 | "gif",
47 | "jpg",
48 | "php",
49 | "php5",
50 | "py",
51 | "rb",
52 | "erb",
53 | ]
54 | let defaultExclusions = [/\.git\//, /\.svn\//, /\.hg\//, /\.DS_Store/];
55 |
56 | const dlog = DEBUG ? (s, str) => { s.config.debug && console.log(str) } : function(){}
57 |
58 | export class Server extends EventEmitter {
59 | constructor(config) {
60 | super()
61 | let extraExts = config.extraExts
62 | if (extraExts) {
63 | delete config.extraExts
64 | }
65 |
66 | this.config = config = Object.assign({
67 | // default options
68 | version: protocol_version,
69 | port: defaultPort,
70 | exts: defaultExts,
71 | exclusions: defaultExclusions,
72 | applyCSSLive: true,
73 | originalPath: "",
74 | overrideURL: "",
75 | usePolling: false,
76 | }, config || {})
77 |
78 | if (extraExts && extraExts.length > 0) {
79 | config.exts = config.exts.concat(extraExts)
80 | }
81 | }
82 |
83 | listen(callback) {
84 | dlog(this, "LiveReload is waiting for a browser to connect...");
85 | dlog(this,
86 | "Protocol version: " + this.config.version + "\nExclusions: " +
87 | this.config.exclusions + "\nExtensions: " + this.config.exts + "\nPolling: " +
88 | this.config.usePolling + "\n"
89 | );
90 | if (this.config.server) {
91 | this.config.server.listen(this.config.port, this.config.bindHost);
92 | this.server = new WebSocketServer({
93 | server: this.config.server
94 | });
95 | } else {
96 | this.server = new WebSocketServer({
97 | port: this.config.port,
98 | host: this.config.bindHost,
99 | });
100 | }
101 | this.server.on('connection', this.onConnection.bind(this));
102 | this.server.on('close', this.onClose.bind(this));
103 | this.server.on('error', this.onError.bind(this));
104 | if (callback) {
105 | return this.server.once('listening', callback);
106 | }
107 | }
108 |
109 | address() {
110 | return this.server && this.server.address()
111 | }
112 |
113 | onError(err) {
114 | dlog(this, "Error " + err);
115 | return this.emit("error", err);
116 | }
117 |
118 | onConnection(socket) {
119 | dlog(this, "Browser connected.");
120 | socket.on('message', (function(_this) {
121 | return function(message) {
122 | var data, request;
123 | dlog(_this, "Client message: " + message);
124 | request = JSON.parse(message);
125 | if (request.command === "hello") {
126 | dlog(_this, "Client requested handshake...");
127 | dlog(_this, "Handshaking with client using protocol " + _this.config.version + "...");
128 | data = JSON.stringify({
129 | command: 'hello',
130 | protocols: ['http://livereload.com/protocols/official-7', 'http://livereload.com/protocols/official-8', 'http://livereload.com/protocols/official-9', 'http://livereload.com/protocols/2.x-origin-version-negotiation', 'http://livereload.com/protocols/2.x-remote-control'],
131 | serverName: 'node-livereload'
132 | });
133 | socket.send(data);
134 | }
135 | if (request.command === "info") {
136 | return dlog(_this, "Server received client data. Not sending response.");
137 | }
138 | };
139 | })(this));
140 | socket.on('error', (function(_this) {
141 | return function(err) {
142 | return dlog(_this, "Error in client socket: " + err);
143 | };
144 | })(this));
145 | return socket.on('close', (function(_this) {
146 | return function(message) {
147 | return dlog(_this, "Client closed connection");
148 | };
149 | })(this));
150 | }
151 |
152 | onClose(socket) {
153 | return dlog(this, "Socket closed.");
154 | }
155 |
156 | watch(dir) {
157 | dlog(this, "Watching " + dir + "...")
158 | this.watcher = fs.watch(dir, { recursive: true }, (event, filename) => {
159 | this.filterRefresh(filename)
160 | })
161 | }
162 |
163 | filterRefresh(filepath) {
164 | var delayedRefresh, exts, fileext;
165 | exts = this.config.exts;
166 | fileext = path.extname(filepath).substring(1);
167 | if (exts.indexOf(fileext) !== -1) {
168 | if (this.config.delay) {
169 | return delayedRefresh = setTimeout((function(_this) {
170 | return function() {
171 | clearTimeout(delayedRefresh);
172 | return _this.refresh(filepath);
173 | };
174 | })(this), this.config.delay);
175 | } else {
176 | return this.refresh(filepath);
177 | }
178 | }
179 | }
180 |
181 | refresh(filepath) {
182 | var data;
183 | dlog(this, "Reloading: " + filepath);
184 | data = JSON.stringify({
185 | command: 'reload',
186 | path: filepath,
187 | liveCSS: this.config.applyCSSLive,
188 | liveImg: this.config.applyImgLive,
189 | originalPath: this.config.originalPath,
190 | overrideURL: this.config.overrideURL
191 | });
192 | return this.sendAllClients(data);
193 | }
194 |
195 | alert(message) {
196 | var data;
197 | dlog(this, "Alert: " + message);
198 | data = JSON.stringify({
199 | command: 'alert',
200 | message: message
201 | });
202 | return this.sendAllClients(data);
203 | }
204 |
205 | sendAllClients(data) {
206 | dlog(this, "broadcasting to all clients: " + data)
207 | this.server.clients.forEach(socket => {
208 | socket.send(data, error => {
209 | if (error) { dlog(this, error) }
210 | })
211 | })
212 | }
213 |
214 | debug(str) {
215 | if (this.config.debug) {
216 | return console.log(str + "\n");
217 | }
218 | }
219 |
220 | close() {
221 | if (this.watcher) {
222 | this.watcher.close();
223 | }
224 | this.server._server.close();
225 | return this.server.close();
226 | }
227 |
228 | } // class
229 |
230 |
231 | export function createServer(config, callback) {
232 | var app, requestHandler, server;
233 | if (config == null) {
234 | config = {};
235 | }
236 | requestHandler = function(req, res) {
237 | if (url.parse(req.url).pathname === '/livereload.js') {
238 | res.writeHead(200, {
239 | 'Content-Type': 'text/javascript'
240 | });
241 | return res.end(fs.readFileSync(__dirname + '/../ext/livereload.js'));
242 | }
243 | };
244 | if (config.https == null) {
245 | app = http.createServer(requestHandler);
246 | } else {
247 | app = https.createServer(config.https, requestHandler);
248 | }
249 | if (config.server == null) {
250 | config.server = app;
251 | }
252 | server = new Server(config);
253 | if (!config.noListen) {
254 | server.listen(callback);
255 | }
256 | return server;
257 | }
258 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { parseopts } from "./parseopts"
2 | import { createServer } from "./server"
3 | import { die, dlog } from "./util"
4 |
5 | const Path = require("path")
6 |
7 | function usage() {
8 | let v = process.argv
9 | let prog = v[0].endsWith("node") ? Path.relative(process.cwd(), Path.resolve(v[1])) : v[0]
10 | let progl = "./" + prog
11 | if (process.env["_"] == progl) {
12 | // common case: ./serve-http (in cwd)
13 | prog = progl
14 | }
15 | prog = Path.basename(prog)
16 | let s = `
17 | Usage: ${prog} [options] []
18 |
19 |
20 | Directory to serve files from. Defaults to the current directory.
21 |
22 | Options:
23 | -p, -port Listen on specific
24 | -host Bind to instead of "localhost"
25 | -public Accept connections from anywhere (same as -host "")
26 | -q, -quiet Don't log requests
27 | -no-livereload Disable livereload
28 | -no-dirlist Disable directory listing
29 | -dirlist-hidden Show files beginning with "." in directory listings
30 | -h, -help Show help and exit
31 | -version Print version to stdout and exit
32 |
33 | Examples:
34 |
35 | ${prog}
36 | Serve current directory on some available port
37 |
38 | ${prog} -p 8080 docs
39 | Serve directory ./docs locally on port 8080
40 |
41 | ${prog} -public -no-dirlist
42 | Serve current directory publicly on some available port,
43 | without directory listing.
44 |
45 | `.trim()+"\n"
46 | if (!WITH_LIVERELOAD) {
47 | s = s.replace(/^\s+livereload.+\n/g, "")
48 | }
49 | console.log(s)
50 | process.exit(0)
51 | }
52 |
53 | const opts = {
54 | // available command-line options with default values
55 | port: 0, p: 0,
56 | host: "localhost",
57 | public: false,
58 | q: false, quiet: false,
59 | noLivereload: false,
60 | noDirlist: false, // disable directory listing
61 | dirlistHidden: false,
62 | version: false,
63 | }
64 |
65 | function main() {
66 | let args = parseopts(process.argv.slice(2), opts, usage)
67 |
68 | if (opts.version) {
69 | console.log(VERSION)
70 | process.exit(0)
71 | }
72 |
73 | let pubdir = ""
74 | if (args.length > 0) {
75 | if (args.length > 1) {
76 | console.error(`ignoring extra arguments: ${args.slice(1).join(" ")}`)
77 | }
78 | pubdir = args[0]
79 | }
80 |
81 | if (opts.public && opts.host == "localhost") {
82 | opts.host = ""
83 | }
84 |
85 | opts.quiet = opts.quiet || opts.q
86 |
87 | const server = createServer({
88 | port: opts.port || opts.p,
89 | host: opts.host,
90 | public: opts.public,
91 | quiet: opts.quiet,
92 | showServingMessage: true,
93 | pubdir,
94 | dirlist: {
95 | disable: opts.noDirlist,
96 | showHidden: opts.dirlistHidden,
97 | },
98 | livereload: {
99 | disable: opts.noLivereload,
100 | },
101 | })
102 |
103 | // stop server and exit cleanly on ^C
104 | process.on('SIGINT', () => {
105 | opts.quiet || console.log(" stopping server")
106 | server.close() // stop accepting new connections
107 | // give ongoing requests a short time to finish processing
108 | setTimeout(() => process.exit(0), 500).unref()
109 | })
110 | }
111 |
112 | if (module.id == ".") {
113 | main()
114 | } else {
115 | module.exports = { createServer }
116 | }
117 |
--------------------------------------------------------------------------------
/src/mime.js:
--------------------------------------------------------------------------------
1 | const plainText = 'text/plain'
2 | const yaml = 'text/x-yaml'
3 | const markdown = 'text/markdown'
4 | const jpeg = 'image/jpeg'
5 | const html = 'text/html'
6 |
7 | export const extToMimeType = {
8 | 'aiff': 'audio/x-aiff',
9 | 'appcache': 'text/cache-manifest',
10 | 'atom': 'application/atom+xml',
11 | 'bmp': 'image/bmp',
12 | 'crx': 'application/x-chrome-extension',
13 | 'css': 'text/css',
14 | 'eot': 'application/vnd.ms-fontobject',
15 | 'gif': 'image/gif',
16 | 'htc': 'text/x-component',
17 | 'html': html, 'htm': html,
18 | 'ico': 'image/vnd.microsoft.icon',
19 | 'ics': 'text/calendar',
20 | 'jpeg': jpeg, 'jpe': jpeg, 'jpg': jpeg,
21 | 'js': 'text/javascript',
22 | 'json': 'application/json',
23 | 'mathml': 'application/mathml+xml',
24 | 'midi': 'audio/midi',
25 | 'mov': 'video/quicktime',
26 | 'mp3': 'audio/mpeg',
27 | 'mp4': 'video/mp4',
28 | 'mpeg': 'video/mpeg',
29 | 'ogg': 'video/ogg',
30 | 'otf': 'font/opentype',
31 | 'pdf': 'application/pdf',
32 | 'png': 'image/png',
33 | 'rtf': 'application/rtf',
34 | 'svg': 'image/svg+xml',
35 | 'swf': 'application/x-shockwave-flash',
36 | 'tar': 'application/x-tar',
37 | 'tiff': 'image/tiff',
38 | 'ttf': 'font/truetype',
39 | 'wav': 'audio/x-wav',
40 | 'webm': 'video/webm',
41 | 'wasm': 'application/wasm',
42 | 'webp': 'image/webp',
43 | 'woff': 'font/woff',
44 | 'woff2': 'font/woff2',
45 | 'xhtml': 'application/xhtml+xml',
46 | 'xml': 'text/xml',
47 | 'xsl': 'application/xml',
48 | 'xslt': 'application/xslt+xml',
49 | 'zip': 'application/zip',
50 | 'txt': plainText,
51 | 'ninja': plainText,
52 | 'md': markdown, 'markdown': markdown, 'mdown': markdown,
53 | 'yaml': yaml, 'yml': yaml,
54 | 'rb': 'text/ruby',
55 | 'ts': 'text/typescript',
56 | 'sh': 'text/x-sh',
57 | 'go': 'text/x-go',
58 | 'py': 'text/x-python',
59 | 'php': 'text/x-php',
60 | }
61 |
62 | extToMimeType["aif"] = extToMimeType["aiff"]
63 | extToMimeType["manifest"] = extToMimeType["appcache"]
64 | extToMimeType["mid"] = extToMimeType["midi"]
65 | extToMimeType["mpg"] = extToMimeType["mpeg"]
66 | extToMimeType["ogv"] = extToMimeType["ogg"]
67 | extToMimeType["svgz"] = extToMimeType["svg"]
68 | extToMimeType["tif"] = extToMimeType["tiff"]
69 | extToMimeType["xht"] = extToMimeType["xhtml"]
70 |
--------------------------------------------------------------------------------
/src/parseopts.js:
--------------------------------------------------------------------------------
1 | import { die } from "./util"
2 |
3 | export function parseopts(argv, opts, usage) {
4 | let args = []
5 | for (let i = 0; i < argv.length; i++) {
6 | let arg = argv[i]
7 | if (arg[0] == "-") {
8 | let k = arg.replace(/^-+/, "")
9 | if (k == "h" || k == "help") {
10 | usage()
11 | }
12 | if (!(k in opts)) {
13 | k = k.replace(/-(\w)/g, (_, m) => m[0].toUpperCase())
14 | }
15 | if (k in opts) {
16 | let t = typeof opts[k]
17 | let v = true
18 | if (t != "boolean") {
19 | if ((v = argv[++i]) === undefined) {
20 | die(`missing value for option ${arg}`)
21 | }
22 | if (t == "number") {
23 | v = Number(v)
24 | if (isNaN(v)) {
25 | die(`invalid value ${argv[i]} for option ${arg} (not a number)`)
26 | }
27 | }
28 | }
29 | opts[k] = v
30 | } else {
31 | die(`unknown option ${arg}`)
32 | }
33 | } else {
34 | args.push(arg)
35 | }
36 | }
37 | return args
38 | }
39 |
--------------------------------------------------------------------------------
/src/safe.js:
--------------------------------------------------------------------------------
1 | const os = require("os")
2 | const fs = require("fs")
3 | const Path = require("path")
4 |
5 | export function checkSafePubdir(pubdirRealpath) {
6 | // If this socket will be opened to the local network, only allow serving a
7 | // directory inside the home directory. Prevent people from serving up their
8 | // SSH keys to the local network unintentionally.
9 | let home = os.homedir()
10 | if (!home) {
11 | return "no home directory"
12 | }
13 |
14 | home = fs.realpathSync(Path.resolve(home))
15 | if (!pubdirRealpath.startsWith(home)) {
16 | return "directory not contained inside your home directory"
17 | }
18 |
19 | // Not sure why someone would do this but it's not a good idea
20 | if (pubdirRealpath === Path.join(home, '.ssh')) {
21 | return "directory is inside SSH directory"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | import { fmt } from "./fmt"
2 | import { extToMimeType } from "./mime"
3 | import { checkSafePubdir } from "./safe"
4 | import { die, addrToURL, dlog, stat } from "./util"
5 | import { createDirectoryListingHTML } from "./dirlist"
6 | import { createServer as createLivereloadServer } from "./livereload"
7 | import livereloadJSBody from "../build/livereload-script"
8 |
9 | const createHttpServer = require("http").createServer
10 | const urlparse = require("url").parse
11 | const Path = require("path")
12 | const os = require("os")
13 | const fs = require("fs")
14 | const promisify = require("util").promisify
15 |
16 | const readfile = promisify(fs.readFile)
17 | const writefile = promisify(fs.writeFile)
18 |
19 | let pubdir = "" // absolute path
20 | let server = null
21 | let livereloadServer = null
22 | let log = ()=>{}
23 |
24 |
25 | export function createServer(opts) {
26 | opts = opts ? Object.assign({}, opts) : {} // copy
27 | opts.host = opts.host || (opts.public ? "" : "localhost")
28 | opts.port = opts.port && opts.port > 0 ? opts.port : undefined
29 | opts.livereload = (
30 | opts.livereload && typeof opts.livereload == "object" ? opts.livereload :
31 | { disable: opts.livereload !== undefined && !opts.livereload }
32 | )
33 | opts.dirlist = (
34 | opts.dirlist && typeof opts.dirlist == "object" ? opts.dirlist :
35 | { disable: opts.dirlist !== undefined && !opts.dirlist }
36 | )
37 |
38 | pubdir = Path.resolve(opts.pubdir || ".")
39 | try {
40 | pubdir = fs.realpathSync(pubdir)
41 | } catch (err) {
42 | if (err.code == "ENOENT") {
43 | die(`Directory ${pubdir} does not exist`)
44 | }
45 | throw err
46 | }
47 |
48 | let handler = formHandlerChain([
49 | !opts.quiet && requestLogger(),
50 | handleRequest,
51 | ])
52 |
53 | let handler2 = (req, res) => handler(req, res).catch(err => {
54 | console.error("Internal server error:", err.stack||String(err))
55 | return endResponse(res, 500, `Internal server error: ${err}`)
56 | })
57 |
58 | server = createHttpServer(handler2)
59 | server.options = opts
60 |
61 | server.localOnly = true
62 | if (opts.host != "localhost" && opts.host != "127.0.0.1" && opts.host != "::1") {
63 | server.localOnly = false
64 | if (!opts.public) {
65 | let msg = checkSafePubdir(pubdir)
66 | if (msg) {
67 | die("Refusing to allow external connections for security reasons:\n " +
68 | msg.replace(/\.$/,".") + "." +
69 | "\n Set -public to ignore this safeguard. Please be careful.")
70 | }
71 | }
72 | }
73 |
74 | server.listen(opts.port, opts.host, () => {
75 | let addr = server.address()
76 |
77 | // livereload port
78 | let lrport = 0
79 | if (WITH_LIVERELOAD && !opts.livereload.disable) {
80 | lrport = (
81 | opts.livereload.port ? opts.livereload.port :
82 | opts.port ? Math.min(65535, opts.port + 10000) :
83 | addr.port >= 65535 ? addr.port - 1 :
84 | addr.port + 1
85 | )
86 | startLivereloadServer(lrport, opts.host)
87 | }
88 |
89 | if (opts.showServingMessage) {
90 | let dir = Path.relative(".", pubdir)
91 | if (dir[0] != ".") {
92 | dir = "./" + dir
93 | } else if (dir == ".." || dir.startsWith("../")) {
94 | dir = pubdir
95 | let homedir = os.homedir()
96 | if (homedir == dir) {
97 | dir = "~/"
98 | } else if (homedir && dir.startsWith(homedir)) {
99 | dir = "~" + dir.substr(homedir.length)
100 | }
101 | }
102 | let lrmsg = lrport ? ` (livereload on :${lrport})` : ""
103 | console.log(
104 | `serving %s%s at %s/${lrmsg}`,
105 | dir,
106 | server.localOnly ? "" : " PUBLICLY TO THE INTERNET",
107 | addrToURL(addr)
108 | )
109 | }
110 |
111 | // DEBUG && setTimeout(() => {
112 | // require("http").get("http://localhost:" + addr.port + "/dir/", async res => {
113 | // dlog("headers:\n %s",
114 | // Object.keys(res.headers).map(k => `${k}: ${res.headers[k]}`).join("\n ")
115 | // )
116 | // let body = await readStream(res)
117 | // console.log("body:", body.toString("utf8"))
118 | // })
119 | // }, 100)
120 | })
121 |
122 | server.once("close", () => {
123 | if (livereloadServer)
124 | livereloadServer.close()
125 | })
126 |
127 | return server
128 | }
129 |
130 |
131 | function startLivereloadServer(port, bindHost) {
132 | if (WITH_LIVERELOAD) {
133 | livereloadServer = createLivereloadServer({
134 | port,
135 | bindHost,
136 | }, () => {
137 | livereloadServer.watch(pubdir)
138 | })
139 | }
140 | }
141 |
142 |
143 | function formHandlerChain(handlers) {
144 | // [ h1, h2, h3 ]
145 | // ->
146 | // (req, res) =>
147 | // h1(req, res).then(() =>
148 | // h2(req, res).then(() =>
149 | // h3(req, res)))
150 | //
151 | handlers = handlers.filter(f => !!f)
152 | if (handlers.length == 1) {
153 | return handlers[0]
154 | }
155 | return handlers.reduce((prev, next) =>
156 | (req, res) =>
157 | prev(req, res).then((req1, res1) =>
158 | next(req1||req, res1||res) )
159 | )
160 | }
161 |
162 |
163 | function requestLogger() {
164 | return async (req, res) => {
165 | let end = res.end, writeHead = res.writeHead, statusCode
166 |
167 | res.writeHead = (code, headers) => {
168 | res.writeHead = writeHead
169 | res.writeHead(code, headers)
170 | res.__statusCode = statusCode = code
171 | res.__headers = headers || {}
172 | }
173 |
174 | res.end = (chunk, encoding) => {
175 | res.end = end
176 | res.end(chunk, encoding)
177 | let addr = (
178 | req.socket &&
179 | (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress))
180 | )
181 | let time = (new Date).toUTCString()
182 | let httpv = req.httpVersionMajor + '.' + req.httpVersionMinor
183 | let status = statusCode || res.statusCode
184 | let ua = req.headers['user-agent'] || '-'
185 | console.log(`${addr} [${time}] "${req.method} ${req.url} HTTP/${httpv}" ${status} ${ua}`)
186 | }
187 | }
188 | }
189 |
190 |
191 | function endResponse(res, status, str) {
192 | let body = str ? Buffer.from(str.trim() + "\n", "utf8") : undefined
193 | res.writeHead(status, {
194 | "Content-Type": "text/plain",
195 | "Content-Length": body ? String(body.length) : "0",
196 | })
197 | res.end(body)
198 | }
199 |
200 |
201 | function replyNotFound(req, res) {
202 | endResponse(res, 404, `404 not found ${req.pathname}`, "utf8")
203 | }
204 |
205 |
206 | function readStream(req) {
207 | return new Promise(resolve => {
208 | if (req.body && req.body instanceof Buffer) {
209 | return resolve(req.body)
210 | }
211 | let body = []
212 | req.on('data', data => body.push(data))
213 | req.on('end', () => {
214 | resolve(req.body = Buffer.concat(body))
215 | })
216 | })
217 | }
218 |
219 |
220 | async function handleRequest(req, res) {
221 | req.pathname = decodeURIComponent(urlparse(req.url).pathname).replace(/^\.{2,}|\.{2,}$/g, "")
222 |
223 | // Only allow writing over files if the server can only accept local connections
224 | if (req.method === "POST" && server.localOnly) {
225 | return handlePOSTFileRequest(req, res)
226 | }
227 |
228 | if (req.method === "GET" || req.method === "HEAD") {
229 | if (WITH_LIVERELOAD && livereloadServer && req.pathname == "/livereload.js") {
230 | return handleGETLivereload(req, res)
231 | }
232 | return handleGETFileRequest(req, res)
233 | }
234 |
235 | endResponse(res, 500, `Unsupported method ${req.method}`)
236 | }
237 |
238 |
239 | let livereloadRes = null
240 |
241 | async function handleGETLivereload(req, res) {
242 | if (WITH_LIVERELOAD) {
243 | if (!livereloadRes) {
244 | let body = Buffer.from(livereloadJSBody, "utf8")
245 | livereloadRes = {
246 | body,
247 | header: {
248 | "Content-Type": "text/javascript",
249 | "Content-Length": String(body.length),
250 | }
251 | }
252 | }
253 | res.writeHead(200, livereloadRes.header)
254 | res.end(livereloadRes.body)
255 | }
256 | }
257 |
258 |
259 | async function handleGETFileRequest(req, res) {
260 | let filename = Path.join(pubdir, req.pathname)
261 | let st = await stat(filename)
262 | if (!st) {
263 | return replyNotFound(req, res)
264 | }
265 | if (st.isFile()) {
266 | return serveFile(req, res, filename, st)
267 | }
268 | if (st.isDirectory()) {
269 | return serveDir(req, res, filename, st)
270 | }
271 | endResponse(res, 404, `404 not found ${req.pathname} (unreadable file type)`, "utf8")
272 | }
273 |
274 |
275 | async function handlePOSTFileRequest(req, res) {
276 | // Write files with POST. Example:
277 | // curl -X POST -H "Content-Type: text/plain" -d "Hello" http://localhost:8090/hello.txt
278 | //
279 | let remoteAddr = req.socket.address().address
280 | if (remoteAddr != "127.0.0.1" && remoteAddr != "::1") {
281 | return endResponse(res, 403, "Forbidden")
282 | }
283 |
284 | let origin = req.headers.origin && urlparse(req.headers.origin).hostname
285 | if (origin) {
286 | // if (origin !== 'localhost' && origin !== '127.0.0.1' && origin !== "::1") {
287 | // return endResponse(res, 403, "Forbidden")
288 | // }
289 | res.setHeader('Access-Control-Allow-Origin', origin)
290 | }
291 |
292 | let filename = Path.join(pubdir, req.pathname)
293 | let [st, body] = await Promise.all([
294 | stat(filename),
295 | readStream(req)
296 | ])
297 | if (st && st.isDirectory()) {
298 | return endResponse(res, 409, "Conflict: Directory exists at path")
299 | }
300 | await writefile(filename, body)
301 | if (st) {
302 | endResponse(res, 200, "File replaced")
303 | } else {
304 | endResponse(res, 201, "File created")
305 | }
306 | }
307 |
308 |
309 | function isProbablyUTF8Text(buf) {
310 | for (let i = 0; i < Math.min(4096, buf.length); i++) {
311 | let b = buf[i]
312 | if (b <= 0x08) { return false }
313 | if (b >= 0x80 && ((b >> 5) != 0x6 || (b >> 4) != 0xe || (b >> 3) != 0x1e)) {
314 | return false // not UTF-8
315 | }
316 | }
317 | return true
318 | }
319 |
320 |
321 | async function serveFile(req, res, filename, st) {
322 | const opts = server.options
323 | res.statusCode = 200
324 |
325 | let body = await readfile(filename)
326 |
327 | const mimeType = (
328 | extToMimeType[Path.extname(filename).substr(1)] ||
329 | opts.defaultMimeType ||
330 | (isProbablyUTF8Text(body) ? "text/plain; charset=utf-8" : "application/octet-stream")
331 | )
332 |
333 | res.setHeader('Content-Type', mimeType)
334 | res.setHeader('Last-Modified', st.mtime.toUTCString())
335 | res.setHeader('ETag', etag(st))
336 |
337 | if (mimeType == "text/html" && !opts.nolivereload) {
338 | body = preprocessHtml(body)
339 | }
340 |
341 | res.setHeader('Content-Length', body.length)
342 |
343 | if (req.method == "HEAD") {
344 | res.end()
345 | return
346 | }
347 |
348 | if (opts.emulateSlowConnection) {
349 | let chunkTime = 1000 // Stream each file out over a second
350 | let chunkCount = 100 // The number of chunks to deliver the file in
351 | let chunkSize = Math.ceil(body.length / chunkCount)
352 | function next() {
353 | if (body.length > 0) {
354 | res.write(body.slice(0, chunkSize))
355 | body = body.slice(chunkSize)
356 | setTimeout(next, chunkTime / chunkCount)
357 | } else {
358 | res.end()
359 | }
360 | }
361 | return next()
362 | }
363 |
364 | res.end(body)
365 | }
366 |
367 |
368 | async function serveDir(req, res, filename, st) {
369 | dlog("serveDir %r", req.pathname)
370 | const opts = server.options
371 | const indexFilename = opts.indexFilename || "index.html"
372 |
373 | let indexFile = Path.join(filename, indexFilename)
374 | let indexFileSt = await stat(indexFile)
375 | if (indexFileSt && indexFileSt.isFile()) {
376 | return serveFile(req, res, indexFile, indexFileSt)
377 | }
378 |
379 | if (opts.dirlist.disable) {
380 | return replyNotFound(req, res)
381 | }
382 |
383 | if (req.pathname[req.pathname.length - 1] != "/") {
384 | // redirect to "/"
385 | res.writeHead(303, {
386 | "Expires": "Sun, 11 Mar 1984 12:00:00 GMT",
387 | "Location": req.pathname + "/",
388 | "Content-Length": "0",
389 | })
390 | res.end()
391 | return
392 | }
393 |
394 | let body = await createDirectoryListingHTML(filename, req.pathname, opts.dirlist)
395 | body = Buffer.from(body, "utf8")
396 | res.writeHead(200, {
397 | "Expires": "Sun, 11 Mar 1984 12:00:00 GMT",
398 | "Content-Type": "text/html",
399 | "Content-Length": String(body.length),
400 | })
401 | res.end(req.method == "HEAD" ? undefined : body)
402 | }
403 |
404 |
405 | function etag(st) {
406 | return `"${st.mtimeMs.toString(36)}-${st.ino.toString(36)}-${st.size.toString(36)}"`
407 | }
408 |
409 |
410 | function preprocessHtml(body) {
411 | // add livereload script to html
412 | let s = body.toString("utf8")
413 | let i = s.indexOf("")
414 | if (i == -1) { i = s.indexOf("