@vindo/cli 0.0.1
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/README.md +12 -0
- package/bin/build.js +139 -0
- package/bin/bundle.js +83 -0
- package/bin/config.js +58 -0
- package/bin/index.js +191 -0
- package/bin/reload.js +6 -0
- package/bin/server.js +43 -0
- package/package.json +31 -0
package/README.md
ADDED
package/bin/build.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const path = require('node:path')
|
|
4
|
+
const {file} = require('@vindo/utility')
|
|
5
|
+
const {writeFile} = require('node:fs/promises')
|
|
6
|
+
const {build:esbuild} = require('esbuild')
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const option = {
|
|
10
|
+
bundle: true,
|
|
11
|
+
format: 'esm',
|
|
12
|
+
logLevel: 'error',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolve path
|
|
18
|
+
*/
|
|
19
|
+
function resolve(...args) {
|
|
20
|
+
return path.resolve(process.cwd(), ...args)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Directory name
|
|
26
|
+
*/
|
|
27
|
+
function dirname(...args) {
|
|
28
|
+
return path.resolve(__dirname, ...args)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolve alias in the bundle
|
|
34
|
+
*/
|
|
35
|
+
function resolveAlias(buildPath) {
|
|
36
|
+
return {
|
|
37
|
+
name: 'resolve-alias',
|
|
38
|
+
setup(build) {
|
|
39
|
+
build.onResolve({filter: /.*/}, ({path}) => {
|
|
40
|
+
const build_ = /@build/
|
|
41
|
+
|
|
42
|
+
if(path.match(/^node_modules\//)) {
|
|
43
|
+
var absPath = resolve(path, 'index.js')
|
|
44
|
+
|
|
45
|
+
if(!file.exists(absPath)) {
|
|
46
|
+
absPath = resolve(path.concat('.js'))
|
|
47
|
+
}
|
|
48
|
+
return {path: absPath}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if(path.match(build_)) {
|
|
52
|
+
return {
|
|
53
|
+
path: resolve(path.replace(build_, buildPath).concat('.js'))
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create a chunk of components
|
|
65
|
+
* @param {object} conf
|
|
66
|
+
*/
|
|
67
|
+
async function chunk(conf) {
|
|
68
|
+
var imp = ''
|
|
69
|
+
|
|
70
|
+
const out = resolve(conf.output, 'chunk.js')
|
|
71
|
+
const dir = resolve(conf.entries)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if(file.isDir(dir)) {
|
|
75
|
+
const dir_ = conf.entries.split('/')
|
|
76
|
+
|
|
77
|
+
const files = file.readdir(dir)
|
|
78
|
+
if(files.length == 0) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for(var item of files) {
|
|
83
|
+
const name = path.parse(item.name).name
|
|
84
|
+
if(name) {
|
|
85
|
+
imp += `import ${name}_ from '${dir_.slice(1).join('/')}/${name}'\n`
|
|
86
|
+
imp += `export const ${name} = ${name}_\n`
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await writeFile(out, imp, {flag: 'w'})
|
|
92
|
+
await esbuild({
|
|
93
|
+
...option,
|
|
94
|
+
outfile: out,
|
|
95
|
+
packages: 'external',
|
|
96
|
+
entryPoints: [
|
|
97
|
+
out
|
|
98
|
+
],
|
|
99
|
+
allowOverwrite: true
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
return out
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create a bundle file
|
|
108
|
+
* @param {string} outbundle
|
|
109
|
+
* @param {object} conf
|
|
110
|
+
*/
|
|
111
|
+
async function bundle(outbundle, conf) {
|
|
112
|
+
option.minify = conf.minify ?? false
|
|
113
|
+
|
|
114
|
+
const out = await chunk(conf)
|
|
115
|
+
await esbuild({
|
|
116
|
+
...option,
|
|
117
|
+
outfile: resolve(conf.output, outbundle),
|
|
118
|
+
entryPoints: [
|
|
119
|
+
dirname(outbundle)
|
|
120
|
+
],
|
|
121
|
+
plugins: [resolveAlias(conf.output)]
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
if(file.exists(out)) fs.unlinkSync(out)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
*
|
|
130
|
+
* @param {object} conf
|
|
131
|
+
*/
|
|
132
|
+
module.exports = async function build(conf) {
|
|
133
|
+
try {
|
|
134
|
+
await bundle('bundle.js', conf)
|
|
135
|
+
}
|
|
136
|
+
catch(e) {
|
|
137
|
+
console.log(e)
|
|
138
|
+
}
|
|
139
|
+
}
|
package/bin/bundle.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as chunk from '@build/chunk'
|
|
2
|
+
|
|
3
|
+
import ReactDom from 'node_modules/react-dom/client'
|
|
4
|
+
import {
|
|
5
|
+
createElement as element,
|
|
6
|
+
isValidElement as isValid,
|
|
7
|
+
}
|
|
8
|
+
from 'node_modules/react'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const url = new URL(document.scripts.bundle.src)
|
|
12
|
+
|
|
13
|
+
const hash = url.searchParams.get('hash')
|
|
14
|
+
const port = url.searchParams.get('port')
|
|
15
|
+
const name = url.searchParams.get('name')
|
|
16
|
+
|
|
17
|
+
const get = fetch(
|
|
18
|
+
hash.concat('?',
|
|
19
|
+
(new URLSearchParams({name})).toString()
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Set element type
|
|
26
|
+
*/
|
|
27
|
+
function children(data) {
|
|
28
|
+
if(isValid(data)) {
|
|
29
|
+
return data
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if(!Array.isArray(data)) {
|
|
33
|
+
data = [data]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return data.map((item, key) => {
|
|
37
|
+
const props = item.props
|
|
38
|
+
|
|
39
|
+
if(!props) {
|
|
40
|
+
return item
|
|
41
|
+
}
|
|
42
|
+
if(props.children) {
|
|
43
|
+
props.children = children(props.children)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if(!item.type) {
|
|
47
|
+
var name = props.name
|
|
48
|
+
if(!name) {
|
|
49
|
+
throw ReferenceError('Component name is not defined.')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const comp = chunk[name.toLowerCase()]
|
|
53
|
+
if(comp) {
|
|
54
|
+
return element.bind(null, comp)({...props, key})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {...item, key, $$typeof: Symbol.for('react.transitional.element')}
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the props in the backend
|
|
64
|
+
*/
|
|
65
|
+
async function map(res) {
|
|
66
|
+
var data = await res.json()
|
|
67
|
+
if(data) {
|
|
68
|
+
ReactDom.hydrateRoot(document.getElementById('root'), children(data))
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
get.then(map)
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Live reload for development
|
|
75
|
+
*/
|
|
76
|
+
function reload({origin}) {
|
|
77
|
+
if(port) {
|
|
78
|
+
(new EventSource(origin.replace(/\d+/, port))).onmessage = function() {
|
|
79
|
+
location.reload()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
reload(location)
|
package/bin/config.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const {file} = require('@vindo/utility')
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const port = 2001
|
|
5
|
+
const args = {}
|
|
6
|
+
const argv = process.argv
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const vindo = file.read(file.join(process.cwd(), 'vindo.json'))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
for(var i in argv) {
|
|
13
|
+
const pat = /^([a-z-]+)=/
|
|
14
|
+
|
|
15
|
+
if(i < 2) {
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
var arg = argv[i].split(pat, 3).filter(String)
|
|
19
|
+
|
|
20
|
+
if(arg) {
|
|
21
|
+
args[arg[0].substring(2)] = arg[1] ? arg[1] : true
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
/**
|
|
28
|
+
* Development script
|
|
29
|
+
*/
|
|
30
|
+
get devScript() {
|
|
31
|
+
return `http://127.0.0.1:${this.option.port}/browser.js`
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build Options
|
|
36
|
+
*/
|
|
37
|
+
get option() {
|
|
38
|
+
const {buildOption} = JSON.parse(vindo)
|
|
39
|
+
|
|
40
|
+
return Object.assign({port}, buildOption, {
|
|
41
|
+
...args,
|
|
42
|
+
output: typeof args.output == 'string' ? args.output : buildOption.output,
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Console log color pattern
|
|
48
|
+
*/
|
|
49
|
+
pattern: {
|
|
50
|
+
ref: {color: 'cyan', pat: /(<.*>)/g, text: '$1'},
|
|
51
|
+
symbol: {color: 'green', pat: /(Symbol\(.*\))/g, text: '$1'},
|
|
52
|
+
undefined: {color: 'gray', pat: /(undefined)/g, text: '$1'},
|
|
53
|
+
function: {color: 'cyan', pat: /\[(.*)\]/g, text: '[$1]'},
|
|
54
|
+
boolean: {color: 'yellow', pat: /(false|true)/g, text: '$1'},
|
|
55
|
+
string: {color: 'green', pat: /('.*')/g, text: '$1'},
|
|
56
|
+
number: {color: 'yellow', pat: /:\s(-?\d+)/g, text: ': $1'},
|
|
57
|
+
}
|
|
58
|
+
}
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const {file} = require('@vindo/utility')
|
|
6
|
+
const {exec, spawn} = require('node:child_process')
|
|
7
|
+
const {watch:watcher} = require('chokidar')
|
|
8
|
+
const {styleText:color} = require('node:util')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
var evt = {}
|
|
12
|
+
var cwd = process.cwd()
|
|
13
|
+
var child = null
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const build = require('./build')
|
|
17
|
+
const server = require('./server')
|
|
18
|
+
const {option, pattern, devScript} = require('./config')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Set to global environment
|
|
23
|
+
*/
|
|
24
|
+
process.env.DEV_SERVER = devScript
|
|
25
|
+
process.env.DEV_SERVER_PORT = option.port
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Add event
|
|
30
|
+
*/
|
|
31
|
+
function on(name, cb) {
|
|
32
|
+
evt[name] = cb
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Call the event
|
|
37
|
+
*/
|
|
38
|
+
function emit(name, ...args) {
|
|
39
|
+
if(evt[name]) {
|
|
40
|
+
evt[name].call(...args)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Log with colors
|
|
46
|
+
*/
|
|
47
|
+
function log(name, text) {
|
|
48
|
+
if(name) {
|
|
49
|
+
return console.log(color(name, text.toString().trim()))
|
|
50
|
+
}
|
|
51
|
+
return console.log(text)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
function setColors(text) {
|
|
58
|
+
var text = text.toString()
|
|
59
|
+
|
|
60
|
+
for(var item of Object.values(pattern)) {
|
|
61
|
+
text = text.replace(item.pat, color(item.color, item.text))
|
|
62
|
+
}
|
|
63
|
+
return text.trim()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
*
|
|
69
|
+
*/
|
|
70
|
+
async function watch(dirs, cb) {
|
|
71
|
+
try {
|
|
72
|
+
if(!Array.isArray(dirs)) {
|
|
73
|
+
throw TypeError('Expected a value of type `Array` but received a `String`.')
|
|
74
|
+
}
|
|
75
|
+
watcher(dirs, {
|
|
76
|
+
// ignored: (path, stats) => stats?.isFile() && !path.endsWith('.js'), // only watch js files
|
|
77
|
+
persistent: true
|
|
78
|
+
})
|
|
79
|
+
.on('change', (path) => cb({path}))
|
|
80
|
+
}
|
|
81
|
+
catch(e) {
|
|
82
|
+
throw e
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
*
|
|
89
|
+
*/
|
|
90
|
+
function execute() {
|
|
91
|
+
var i = 0
|
|
92
|
+
var sp = exec(option.execute, {
|
|
93
|
+
cwd,
|
|
94
|
+
env: process.env
|
|
95
|
+
})
|
|
96
|
+
log('green', `[vindo] Running ${option.execute}`)
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Emit change event to reload client
|
|
100
|
+
*/
|
|
101
|
+
sp.stdio[1].on('data', (data) => {
|
|
102
|
+
i += 1
|
|
103
|
+
/**
|
|
104
|
+
* Emit only on first log from server,
|
|
105
|
+
* It means the server runs successfully.
|
|
106
|
+
*/
|
|
107
|
+
if(i == 1) {
|
|
108
|
+
emit('change')
|
|
109
|
+
}
|
|
110
|
+
console.log(setColors(data))
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Error data
|
|
115
|
+
*/
|
|
116
|
+
sp.stdio[2].on('data', (data) => {
|
|
117
|
+
var text = data.toString().split(/\n/)
|
|
118
|
+
|
|
119
|
+
for(var i in text) {
|
|
120
|
+
if(i == 0) {
|
|
121
|
+
text[0] = color('red', text[0])
|
|
122
|
+
}
|
|
123
|
+
text[i] = text[i].replace(/(at.*)/, color('gray', '$1'))
|
|
124
|
+
text[i] = text[i].replace(/:\s('.*')/, color('green', ': $1'))
|
|
125
|
+
text[i] = text[i].replace(/\s(-?\d+)/, color('yellow', ' $1'))
|
|
126
|
+
}
|
|
127
|
+
log(null, text.join('\r\n'))
|
|
128
|
+
})
|
|
129
|
+
return sp
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Restart server and rebuild
|
|
135
|
+
*/
|
|
136
|
+
function restart(event) {
|
|
137
|
+
log('gray', `[vindo] File changed '${event.path}'`)
|
|
138
|
+
log('green', `[vindo] Rebuilding...`)
|
|
139
|
+
log('green', `[vindo] Restarting server...`)
|
|
140
|
+
|
|
141
|
+
if(option.output) {
|
|
142
|
+
build(option)
|
|
143
|
+
}
|
|
144
|
+
if(child) {
|
|
145
|
+
if(process.platform === 'win32') {
|
|
146
|
+
spawn('taskkill', ['/pid', child.pid, '/f', '/t'])
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
child.kill('SIGINT')
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
child = execute()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create server
|
|
158
|
+
*/
|
|
159
|
+
const http = server((req, res) => {
|
|
160
|
+
if(req.isStream()) {
|
|
161
|
+
on('change', () => {
|
|
162
|
+
res.write(`data: {changed: true}\n\n`)
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if(req.is('reload')) {
|
|
167
|
+
res.headers({
|
|
168
|
+
'Content-Type':
|
|
169
|
+
'application/javascript'
|
|
170
|
+
})
|
|
171
|
+
res.end(file.read(__dirname.concat('/reload.js')))
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
http.listen(option.port, function() {
|
|
178
|
+
child = execute()
|
|
179
|
+
|
|
180
|
+
if(option.output) {
|
|
181
|
+
build(option)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if(option.watch) {
|
|
185
|
+
log('yellow', '[vindo] v0.0.1')
|
|
186
|
+
log('yellow', `[vindo] Watching at ${option.watch}`)
|
|
187
|
+
log('yellow', `[vindo] Building at ${option.output}`)
|
|
188
|
+
|
|
189
|
+
watch(option.watch, restart)
|
|
190
|
+
}
|
|
191
|
+
})
|
package/bin/reload.js
ADDED
package/bin/server.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const {parse} = require('node:path')
|
|
2
|
+
const {createServer} = require('node:http')
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
module.exports = function(cb) {
|
|
6
|
+
|
|
7
|
+
return createServer((req, res) => {
|
|
8
|
+
const url = parse(req.url)
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check endpoint
|
|
12
|
+
*/
|
|
13
|
+
req.is = function(name = null) {
|
|
14
|
+
return url.name == name || url.base == name
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Set multiple headers
|
|
19
|
+
*/
|
|
20
|
+
res.headers = function(headers) {
|
|
21
|
+
res.setHeaders(new Headers(headers))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Accept event stream
|
|
26
|
+
*/
|
|
27
|
+
req.isStream = function() {
|
|
28
|
+
if(req.is('')) {
|
|
29
|
+
res.headers({
|
|
30
|
+
'Connection': 'keep-alive',
|
|
31
|
+
'Cache-Control': 'no-cache',
|
|
32
|
+
'Content-Type': 'text/event-stream',
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
res.headers({'Access-Control-Allow-Origin': '*'})
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
cb(req, res)
|
|
42
|
+
})
|
|
43
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vindo/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Command line for running vindo server and react SSR for development.",
|
|
5
|
+
"main": "bin/index.js",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\""
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cli"
|
|
14
|
+
],
|
|
15
|
+
"bin": {
|
|
16
|
+
"vindo": "./bin/index.js"
|
|
17
|
+
},
|
|
18
|
+
"author": "Ruel Mindo",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/rmindo/vindo.git",
|
|
23
|
+
"directory": "packages/cli"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/rmindo/vindo/tree/main/packages/cli",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@vindo/utility": "^0.0.6",
|
|
28
|
+
"chokidar": "^4.0.3",
|
|
29
|
+
"esbuild": "^0.25.4"
|
|
30
|
+
}
|
|
31
|
+
}
|