accept vfs and port at command line
Massimo Melina committed
Dec 14, 2021 at 22:11 UTC
46331a4f900360a06f509db17ab4747e10ef86cf
7 files changed
+60
-39
dev-notes.md
-1
@@ -8,7 +8,6 @@
8
- https://www.tutorialspoint.com/koajs/koajs_quick_guide.htm
9
10
# To do
11
-- fix dist
11
- vfs: rename file in source folder
12
- vfs: hide file in source folder
13
- fix: crash if vfs is not loadable
package-lock.json
+21
-7
@@ -10,7 +10,6 @@
10
"license": "GPL-3.0",
11
"dependencies": {
12
"@koa/router": "^10.1.1",
13
- "@types/lodash": "^4.14.178",
13
"fast-glob": "^3.2.7",
14
"koa": "^2.13.4",
15
"koa-better-http-proxy": "^0.2.9",
@@ -19,6 +18,7 @@
18
"koa-static": "^5.0.0",
19
"lodash": "^4.17.21",
20
"mime-types": "^2.1.34",
21
+ "minimist": "^1.2.5",
22
"yaml": "^1.10.2"
23
},
24
"devDependencies": {
@@ -28,7 +28,9 @@
28
"@types/koa-bodyparser": "^4.3.4",
29
"@types/koa-mount": "^4.0.1",
30
"@types/koa-static": "^4.0.2",
31
+ "@types/lodash": "^4.14.178",
32
"@types/mime-types": "^2.1.1",
33
+ "@types/minimist": "^1.2.2",
34
"@types/mocha": "^9.0.0",
35
"@types/node": "^16.11.12",
36
"axios": "^0.24.0",
@@ -322,7 +324,8 @@
324
"node_modules/@types/lodash": {
325
"version": "4.14.178",
326
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz",
325
- "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw=="
327
+ "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==",
328
+ "dev": true
329
},
330
"node_modules/@types/mime": {
331
"version": "1.3.2",
@@ -336,6 +339,12 @@
339
"integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==",
340
"dev": true
341
},
342
+ "node_modules/@types/minimist": {
343
+ "version": "1.2.2",
344
+ "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz",
345
+ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==",
346
+ "dev": true
347
+ },
348
"node_modules/@types/mocha": {
349
"version": "9.0.0",
350
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz",
@@ -1870,8 +1879,7 @@
1879
"node_modules/minimist": {
1880
"version": "1.2.5",
1881
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
1873
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
1874
- "dev": true
1882
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
1883
},
1884
"node_modules/mocha": {
1885
"version": "9.1.3",
@@ -3364,7 +3372,8 @@
3372
"@types/lodash": {
3373
"version": "4.14.178",
3374
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz",
3367
- "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw=="
3375
+ "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==",
3376
+ "dev": true
3377
},
3378
"@types/mime": {
3379
"version": "1.3.2",
@@ -3378,6 +3387,12 @@
3387
"integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==",
3388
"dev": true
3389
},
3390
+ "@types/minimist": {
3391
+ "version": "1.2.2",
3392
+ "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz",
3393
+ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==",
3394
+ "dev": true
3395
+ },
3396
"@types/mocha": {
3397
"version": "9.0.0",
3398
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz",
@@ -4536,8 +4551,7 @@
4551
"minimist": {
4552
"version": "1.2.5",
4553
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
4539
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
4540
- "dev": true
4554
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
4555
},
4556
"mocha": {
4557
"version": "9.1.3",
package.json
+3
-1
@@ -10,7 +10,7 @@
10
"license": "GPL-3.0",
11
"author": "Massimo Melina <a@rejetto.com> (https://rejetto.com)",
12
"scripts": {
13
- "watch-server": "nodemon --ignore tests/ --watch src -e ts,tsx --exec ts-node src/index.ts",
13
+ "watch-server": "nodemon --ignore tests/ --watch src -e ts,tsx --exec ts-node src/index.ts vfs.yaml",
14
"start": "node dist",
15
"build": "tsc",
16
"test": "mocha -r ts-node/register 'tests/**/*.ts'"
@@ -25,6 +25,7 @@
25
"koa-static": "^5.0.0",
26
"lodash": "^4.17.21",
27
"mime-types": "^2.1.34",
28
+ "minimist": "^1.2.5",
29
"yaml": "^1.10.2"
30
},
31
"devDependencies": {
@@ -36,6 +37,7 @@
37
"@types/koa-static": "^4.0.2",
38
"@types/lodash": "^4.14.178",
39
"@types/mime-types": "^2.1.1",
40
+ "@types/minimist": "^1.2.2",
41
"@types/mocha": "^9.0.0",
42
"@types/node": "^16.11.12",
43
"axios": "^0.24.0",
src/apis.ts
+2
-2
@@ -1,6 +1,6 @@
1
import glob from 'fast-glob'
2
import Koa from 'koa'
3
-import { vfs } from './vfs'
3
+import { vfs, VfsNode } from './vfs'
4
import { enforceFinal } from './misc'
5
import { Stats } from 'fs'
6
import { stat } from 'fs/promises'
@@ -28,7 +28,7 @@ export const frontEndApis: ApiHandlers = {
28
let node = vfs.urlToNode(params.path || '/')
29
if (!node)
30
return
31
- const list = await Promise.all((node.children ||[]).map(async node =>
31
+ const list = await Promise.all((node.children ||[]).map(async (node:VfsNode) =>
32
node.source && await stat(node.source).then(
33
statRes => statToFile(node.name, statRes),
34
() => null) ))
src/const.ts
+4
@@ -1,3 +1,5 @@
1
+import minimist from 'minimist'
2
+
3
export const DEV = process.env.NODE_ENV === 'development' ? 'DEV' : ''
4
5
if (DEV)
@@ -6,3 +8,5 @@ if (DEV)
8
const SPECIAL_URI = '/~/'
9
export const FRONTEND_URI = SPECIAL_URI + 'front/'
10
export const API_URI = SPECIAL_URI + 'api/'
11
+
12
+export const argv = minimist(process.argv.slice(2))
src/index.ts
+10
-7
@@ -3,10 +3,11 @@ import mount from 'koa-mount'
3
import bodyParser from 'koa-bodyparser'
4
import { apiMw, frontEndApis } from './apis'
5
import { serveFrontend } from './frontend'
6
-import { API_URI, DEV, FRONTEND_URI } from './const'
7
-import { serveFileFromVfs } from './vfs'
6
+import { API_URI, argv, DEV, FRONTEND_URI } from './const'
7
+import { createReadStream } from 'fs'
8
+import { vfs } from './vfs'
9
9
-const PORT = 80
10
+const PORT = argv.port || 80
11
12
const srv = new Koa()
13
srv.use(async (ctx, next) => {
@@ -19,16 +20,18 @@ srv.use(async (ctx, next) => {
20
srv.use(mount(API_URI, new Koa().use(bodyParser()).use(apiMw(frontEndApis))))
21
22
// serve shared files and front-end files
22
-const serveFiles = serveFileFromVfs()
23
const serveFrontendPrefixed = mount(FRONTEND_URI.slice(0,-1), serveFrontend)
24
srv.use(async (ctx, next) => {
25
+ const { path } = ctx
26
if (ctx.method !== 'GET')
27
return await next()
27
- if (ctx.path.endsWith('/'))
28
+ if (path.endsWith('/'))
29
return await serveFrontend(ctx,next)
29
- if (ctx.path.startsWith(FRONTEND_URI))
30
+ if (path.startsWith(FRONTEND_URI))
31
return await serveFrontendPrefixed(ctx,next)
31
- await serveFiles(ctx,next)
32
+ const source = vfs.urlToNode(decodeURI(path))?.source
33
+ if (source)
34
+ ctx.body = createReadStream(source as string)
35
})
36
37
srv.on('error', err => {
src/vfs.ts
+20
-21
@@ -1,31 +1,41 @@
1
-import Koa from 'koa'
1
+import { argv } from './const'
2
import yaml from 'yaml'
3
-import fs, { createReadStream } from 'fs'
3
+import fs from 'fs/promises'
4
+import { FSWatcher, watch } from 'fs'
5
import { basename } from 'path'
6
7
enum VfsNodeType {
8
root,
9
}
10
10
-interface VfsNode {
11
+export interface VfsNode {
12
type?: VfsNodeType,
13
name?: string,
14
source?: string,
14
- children?: VfsNode[]
15
+ children?: VfsNode[],
16
+ hide?: string | string[],
17
}
18
17
-class Vfs {
19
+export class Vfs {
20
root: VfsNode = {}
21
+ watcher?: FSWatcher
22
20
- load(path: string, watch:boolean=true) {
21
- const data = fs.readFileSync(path, 'utf8')
23
+ constructor(path?:string) {
24
+ if (path)
25
+ this.load(path).then()
26
+ }
27
+
28
+ async load(path: string, watchFile:boolean=true) {
29
+ console.debug('loading vfs')
30
+ this.watcher?.close()
31
+ const data = await fs.readFile(path, 'utf8')
32
this.root = yaml.parse(data)
33
if (!this.root)
34
throw `Couldn't load ${path}`
35
this.root.type = VfsNodeType.root
36
recur(this.root)
27
- if (watch)
28
- fs.watch(path, () => vfs.load(path))
37
+ if (watchFile)
38
+ this.watcher = watch(path, () => this.load(path))
39
40
function recur(node:VfsNode) {
41
if (node.type !== VfsNodeType.root && !node.name && node.source)
@@ -50,15 +60,4 @@ class Vfs {
60
}
61
}
62
53
-export function serveFileFromVfs() : Koa.Middleware {
54
- return async (ctx, next) => {
55
- let { url } = ctx
56
- const path = vfs.urlToNode(decodeURI(url))?.source
57
- if (path)
58
- ctx.body = createReadStream(path as string)
59
- await next()
60
- }
61
-}
62
-
63
-export const vfs = new Vfs()
64
-vfs.load('vfs.yaml')
63
+export const vfs = new Vfs(argv._[0])