epos 1.2.4 â 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eslint.config.js +19 -0
- package/package.json +9 -2
- package/src/kit/kit-server.js +102 -67
package/eslint.config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import globals from 'globals'
|
|
2
|
+
import pluginJs from '@eslint/js'
|
|
3
|
+
|
|
4
|
+
/** @type {import('eslint').Linter.Config[]} */
|
|
5
|
+
export default [
|
|
6
|
+
{ languageOptions: { globals: globals.browser } },
|
|
7
|
+
pluginJs.configs.recommended,
|
|
8
|
+
{
|
|
9
|
+
languageOptions: {
|
|
10
|
+
globals: {
|
|
11
|
+
Self: false,
|
|
12
|
+
process: false,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
rules: {
|
|
16
|
+
'no-unused-labels': 0,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
]
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epos",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"author": "imkost",
|
|
5
5
|
"description": "",
|
|
6
6
|
"keywords": [],
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"scripts": {
|
|
10
|
-
"dev": "node ./src/kit/kit-bin.js"
|
|
10
|
+
"dev": "node ./src/kit/kit-bin.js",
|
|
11
|
+
"lint": "eslint ./src",
|
|
12
|
+
"publish": "npm publish --loglevel=error"
|
|
11
13
|
},
|
|
12
14
|
"bin": {
|
|
13
15
|
"epos": "src/kit/kit-bin.js"
|
|
@@ -27,5 +29,10 @@
|
|
|
27
29
|
"mime": "^4.0.6",
|
|
28
30
|
"prettier": "^3.4.2",
|
|
29
31
|
"ws": "^8.18.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@eslint/js": "^9.18.0",
|
|
35
|
+
"eslint": "^9.18.0",
|
|
36
|
+
"globals": "^15.14.0"
|
|
30
37
|
}
|
|
31
38
|
}
|
package/src/kit/kit-server.js
CHANGED
|
@@ -6,48 +6,87 @@ import $yaml from 'js-yaml'
|
|
|
6
6
|
import $chokidar from 'chokidar'
|
|
7
7
|
import * as $ws from 'ws'
|
|
8
8
|
|
|
9
|
-
// TODO: handle 'port in use' error
|
|
10
|
-
// TODO: epos-kit as separate package (?) but bin: epos
|
|
11
9
|
const $server = {
|
|
12
|
-
async init(dir
|
|
10
|
+
async init(dir) {
|
|
13
11
|
this._dir = dir
|
|
14
|
-
this.
|
|
15
|
-
this._httpPort = 2077
|
|
12
|
+
this._port = 4322
|
|
16
13
|
this._maxFiles = 10_000
|
|
14
|
+
|
|
17
15
|
this._pkgs = {} // { [path]: { name, dir, watcher } }
|
|
18
|
-
|
|
16
|
+
this._server = null
|
|
17
|
+
this._wss = null
|
|
18
|
+
|
|
19
|
+
await this._initServer()
|
|
20
|
+
await this._initWebSocket()
|
|
21
|
+
await this._startWatcher()
|
|
22
|
+
|
|
23
|
+
console.log('đĸ ready')
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
async _initServer() {
|
|
27
|
+
const ready = Promise.withResolvers()
|
|
28
|
+
|
|
29
|
+
this._server = $http.createServer(async (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const { data, type } = await this._handleRequest(req)
|
|
32
|
+
res.writeHead(200, { 'Content-Type': type })
|
|
33
|
+
res.end(data)
|
|
34
|
+
} catch (e) {
|
|
35
|
+
if (e === 404) {
|
|
36
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' })
|
|
37
|
+
res.end('404: File Not Found')
|
|
38
|
+
} else {
|
|
39
|
+
console.error(e)
|
|
40
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' })
|
|
41
|
+
res.end('500: Internal Server Error')
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
this._server.listen(this._port, () => {
|
|
47
|
+
ready.resolve()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
this._server.on('error', e => {
|
|
51
|
+
if (e.code === 'EADDRINUSE') {
|
|
52
|
+
console.log(`đ´ port ${this._port} is already in use`)
|
|
53
|
+
process.exit()
|
|
54
|
+
}
|
|
55
|
+
ready.reject(e)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
await ready.promise
|
|
19
59
|
},
|
|
20
60
|
|
|
21
|
-
async
|
|
22
|
-
|
|
23
|
-
|
|
61
|
+
async _initWebSocket() {
|
|
62
|
+
this._wss = new $ws.WebSocketServer({ server: this._server })
|
|
63
|
+
},
|
|
24
64
|
|
|
25
|
-
|
|
65
|
+
async _startWatcher() {
|
|
66
|
+
const ready = Promise.withResolvers()
|
|
26
67
|
const watcher = $chokidar.watch(this._dir, { ignored: this._ignored })
|
|
27
68
|
|
|
28
69
|
// initial scan
|
|
29
|
-
let
|
|
30
|
-
let
|
|
31
|
-
watcher.on('
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
console.
|
|
36
|
-
console.
|
|
37
|
-
console.error(`Please point to a directory with less files.`)
|
|
70
|
+
let done = false
|
|
71
|
+
let files = 0
|
|
72
|
+
watcher.on('add', async () => {
|
|
73
|
+
if (done) return
|
|
74
|
+
files += 1
|
|
75
|
+
if (files < this._maxFiles) return
|
|
76
|
+
console.log(`đ´ too many files in ${this._dir}`)
|
|
77
|
+
console.log('âšī¸ select directory with fewer files')
|
|
38
78
|
process.exit()
|
|
39
79
|
})
|
|
40
80
|
watcher.on('ready', () => {
|
|
41
|
-
|
|
42
|
-
|
|
81
|
+
done = false
|
|
82
|
+
ready.resolve()
|
|
43
83
|
})
|
|
44
84
|
|
|
45
85
|
// watch added manifest files
|
|
46
86
|
const variants = new Set(['epos.json', 'epos.yaml', 'epos.yml'])
|
|
47
87
|
watcher.on('add', async path => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const pkg = await this._createPkgWatcher(path, wss)
|
|
88
|
+
if (!variants.has($path.basename(path))) return
|
|
89
|
+
const pkg = await this._createPkgWatcher(path)
|
|
51
90
|
if (!pkg) return
|
|
52
91
|
this._pkgs[path] = pkg
|
|
53
92
|
})
|
|
@@ -59,67 +98,63 @@ const $server = {
|
|
|
59
98
|
delete this._pkgs[path]
|
|
60
99
|
})
|
|
61
100
|
|
|
62
|
-
|
|
63
|
-
const httpServer = $http.createServer(async (req, res) => {
|
|
64
|
-
const pkgName = req.url.split('/')[1]
|
|
65
|
-
const pkg = Object.values(this._pkgs).find(p => p.name === pkgName)
|
|
66
|
-
if (!pkg) {
|
|
67
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' })
|
|
68
|
-
res.end('404: File Not Found')
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const filePath = $path.join(pkg.dir, req.url.split('/').slice(2).join('/'))
|
|
73
|
-
const contentType = $mime.getType(filePath) || 'application/octet-stream'
|
|
74
|
-
try {
|
|
75
|
-
const data = await $fs.readFile(filePath)
|
|
76
|
-
res.writeHead(200, { 'Content-Type': contentType })
|
|
77
|
-
res.end(data)
|
|
78
|
-
} catch (e) {
|
|
79
|
-
if (e.code === 'ENOENT') {
|
|
80
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' })
|
|
81
|
-
res.end('404: File Not Found')
|
|
82
|
-
} else {
|
|
83
|
-
res.writeHead(500, { 'Content-Type': 'text/plain' })
|
|
84
|
-
res.end('500: Internal Server Error')
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
httpServer.listen(this._httpPort, () => {
|
|
89
|
-
httpServerReady.resolve()
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
await watcherReady.promise
|
|
93
|
-
await httpServerReady.promise
|
|
94
|
-
|
|
95
|
-
console.log('⥠running')
|
|
101
|
+
await ready.promise
|
|
96
102
|
},
|
|
97
103
|
|
|
98
|
-
async _createPkgWatcher(manifestPath
|
|
104
|
+
async _createPkgWatcher(manifestPath) {
|
|
105
|
+
// read manifest
|
|
99
106
|
const isJson = manifestPath.endsWith('.json')
|
|
100
107
|
const content = await $fs.readFile(manifestPath, 'utf-8')
|
|
101
108
|
const manifest = isJson ? JSON.parse(content) : $yaml.load(content)
|
|
102
|
-
const pkgName = manifest.name
|
|
103
|
-
if (!pkgName) return null
|
|
104
109
|
|
|
110
|
+
// no name? -> ignore
|
|
111
|
+
const name = manifest.name
|
|
112
|
+
if (!name) return null
|
|
113
|
+
|
|
114
|
+
// create pkg dir watcher
|
|
105
115
|
const dir = $path.dirname(manifestPath)
|
|
106
116
|
const watcher = $chokidar.watch(dir, {
|
|
107
117
|
ignored: this._ignored,
|
|
108
118
|
ignoreInitial: true,
|
|
109
119
|
})
|
|
110
120
|
|
|
121
|
+
// broadcast changes
|
|
111
122
|
watcher.on('all', (event, path) => {
|
|
112
|
-
const data = JSON.stringify({ name
|
|
113
|
-
for (const client of
|
|
123
|
+
const data = JSON.stringify({ name, path: $path.relative(dir, path) })
|
|
124
|
+
for (const client of this._wss.clients) {
|
|
114
125
|
if (client.readyState !== 1) continue
|
|
115
126
|
client.send(data)
|
|
116
127
|
}
|
|
117
128
|
})
|
|
118
129
|
|
|
119
|
-
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
return { name, dir, watcher }
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
async _handleRequest(req) {
|
|
134
|
+
// /<pkgName>/some/path/to/file.jsx
|
|
135
|
+
const [pkgName, ...filePath] = req.url.split('/').slice(1)
|
|
136
|
+
|
|
137
|
+
// pkg not found? -> 404
|
|
138
|
+
const pkg = Object.values(this._pkgs).find(p => p.name === pkgName)
|
|
139
|
+
if (!pkg) throw 404
|
|
140
|
+
|
|
141
|
+
// file not found? -> 404
|
|
142
|
+
const path = $path.join(pkg.dir, ...filePath)
|
|
143
|
+
const exists = await this._fileExists(path)
|
|
144
|
+
if (!exists) throw 404
|
|
145
|
+
|
|
146
|
+
// respond file
|
|
147
|
+
const data = await $fs.readFile(path)
|
|
148
|
+
const type = $mime.getType(path) || 'application/octet-stream'
|
|
149
|
+
return { data, type }
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
async _fileExists(path) {
|
|
153
|
+
try {
|
|
154
|
+
await $fs.access(path, $fs.constants.F_OK)
|
|
155
|
+
return true
|
|
156
|
+
} catch {
|
|
157
|
+
return false
|
|
123
158
|
}
|
|
124
159
|
},
|
|
125
160
|
|