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.
@@ -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.2.4",
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
  }
@@ -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 = '/Users/imkost/z/epos') {
10
+ async init(dir) {
13
11
  this._dir = dir
14
- this._wsPort = 2076
15
- this._httpPort = 2077
12
+ this._port = 4322
16
13
  this._maxFiles = 10_000
14
+
17
15
  this._pkgs = {} // { [path]: { name, dir, watcher } }
18
- await this._start()
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 _start() {
22
- const watcherReady = Promise.withResolvers()
23
- const httpServerReady = Promise.withResolvers()
61
+ async _initWebSocket() {
62
+ this._wss = new $ws.WebSocketServer({ server: this._server })
63
+ },
24
64
 
25
- const wss = new $ws.WebSocketServer({ port: this._wsPort })
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 isInitialScan = true
30
- let initialFileCount = 0
31
- watcher.on('all', async (event, path) => {
32
- if (!isInitialScan) return
33
- initialFileCount += 1
34
- if (initialFileCount < this._maxFiles) return
35
- console.error('⛔ too many files')
36
- console.error(`More than ${this._maxFiles} files in the directory.`)
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
- isInitialScan = false
42
- watcherReady.resolve()
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
- const isEposManifest = variants.has($path.basename(path))
49
- if (!isEposManifest) return
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
- // static server
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, wss) {
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: pkgName, path: $path.relative(dir, path) })
113
- for (const client of wss.clients) {
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
- watcher,
121
- name: pkgName,
122
- dir,
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