@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 ADDED
@@ -0,0 +1,12 @@
1
+ ## Vindo CLI
2
+ Command line for running vindo server and react SSR for development.
3
+
4
+
5
+ ## Installation
6
+ ```
7
+ npm install @vindo/cli
8
+ ```
9
+ Or
10
+ ```
11
+ yarn add @vindo/cli
12
+ ```
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
@@ -0,0 +1,6 @@
1
+ function reload({origin}) {
2
+ (new EventSource(origin)).onmessage = function() {
3
+ location.reload()
4
+ }
5
+ }
6
+ reload(new URL(document.currentScript.src))
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
+ }