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])