biz-a-cli 2.3.69 → 2.3.71

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/bin/app.js CHANGED
@@ -10,7 +10,19 @@ import { env } from "../envs/env.js"
10
10
  import { prepareScript, encryptScript } from "./script.js"
11
11
  import { spawn } from "node:child_process"
12
12
 
13
- const keyFolderPath = process.argv[1].substring(0, process.argv[1].lastIndexOf("\\bin")) + "\\key"
13
+ const getKeyFolderPath = () => {
14
+ const scriptPath = (typeof process.argv[1] === 'string' && process.argv[1].length > 0)
15
+ ? process.argv[1]
16
+ : path.join(import.meta.dirname, 'app.js')
17
+ const normalizedPath = scriptPath.replaceAll('/', '\\')
18
+ const binPathPos = normalizedPath.lastIndexOf('\\bin')
19
+ if (binPathPos >= 0) {
20
+ return normalizedPath.substring(0, binPathPos) + '\\key'
21
+ }
22
+ return path.resolve(import.meta.dirname, '..', 'key')
23
+ }
24
+
25
+ const keyFolderPath = getKeyFolderPath()
14
26
 
15
27
  const options = {
16
28
  "s": {
@@ -69,174 +81,401 @@ const removeCommandOptions = {
69
81
  }
70
82
  }
71
83
 
72
- Object
73
- .keys(options)
74
- .reduce((app, optKey) => app = app.option(optKey, options[optKey]), yargs(process.argv.slice(2)))
75
- .command('add', 'Add Biz-A Application', addCommandOptions, async (options) => {
76
-
77
- const prepareKeys = async () => {
78
- const data = Buffer.from(JSON.stringify({ issuer: 'CLI', acquirer: 'Client' })).toString('base64')
79
- const privateKey = fs.readFileSync(`${keyFolderPath}\\cliPrivate.pem`)
80
- const signature = sign('sha256', data, { key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_PSS_PADDING }).toString('base64')
81
- const res = await axios.get(env.BIZA_SERVER_LINK + '/api/issuerKey', { params: { data, signature } })
82
- if ((res.data.data != null) && verify('sha256', res.data.data, { key: fs.readFileSync(`${keyFolderPath}\\serverPublic.pem`), padding: cryptoConstants.RSA_PKCS1_PSS_PADDING }, Buffer.from(res.data.signature, 'base64'))) {
83
- const resData = JSON.parse(Buffer.from(res.data.data, 'base64').toString())
84
- const decryptedAESKey = privateDecrypt({ key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_OAEP_PADDING }, Buffer.from(resData.issuer.key, 'base64')).toString()
85
- const cliSignature = (signedData) => sign('sha256', signedData, { key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_PSS_PADDING }).toString('base64')
86
- const acquirerData = Buffer.from(JSON.stringify(resData.acquirer)).toString('base64')
87
- const signature = cliSignature(acquirerData)
88
- return { encryptKey: decryptedAESKey, metadata: { acquirer: { data: acquirerData, signature } } }
89
- }
90
- else {
91
- return null
84
+ const prepareKeys = async () => {
85
+ const data = Buffer.from(JSON.stringify({ issuer: 'CLI', acquirer: 'Client' })).toString('base64')
86
+ const privateKey = fs.readFileSync(`${keyFolderPath}\\cliPrivate.pem`)
87
+ const signature = sign('sha256', data, { key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_PSS_PADDING }).toString('base64')
88
+ const res = await axios.get(env.BIZA_SERVER_LINK + '/api/issuerKey', { params: { data, signature } })
89
+ if ((res.data.data != null) && verify('sha256', res.data.data, { key: fs.readFileSync(`${keyFolderPath}\\serverPublic.pem`), padding: cryptoConstants.RSA_PKCS1_PSS_PADDING }, Buffer.from(res.data.signature, 'base64'))) {
90
+ const resData = JSON.parse(Buffer.from(res.data.data, 'base64').toString())
91
+ const decryptedAESKey = privateDecrypt({ key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_OAEP_PADDING }, Buffer.from(resData.issuer.key, 'base64')).toString()
92
+ const cliSignature = (signedData) => sign('sha256', signedData, { key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_PSS_PADDING }).toString('base64')
93
+ const acquirerData = Buffer.from(JSON.stringify(resData.acquirer)).toString('base64')
94
+ const signature = cliSignature(acquirerData)
95
+ return { encryptKey: decryptedAESKey, metadata: { acquirer: { data: acquirerData, signature } } }
96
+ }
97
+ else {
98
+ return null
99
+ }
100
+ }
101
+
102
+ const compressIt = (fileName, folderPath) => {
103
+ // tar v7.1.0 -> tested 10x times, 2-3x times last compressed file have empty content
104
+ // tar v.7.4.0 -> tested 1000x times, all files have content (used app.test.js)
105
+ let compressSuccess = false
106
+ const maxRetry = 10
107
+ let retryCount = 0
108
+ do {
109
+ tar.c({ file: fileName, cwd: folderPath, gzip: { level: 9 }, strict: true, sync: true }, fs.readdirSync(folderPath))
110
+ compressSuccess = true
111
+ tar.t({
112
+ file: fileName, cwd: folderPath, sync: true, onentry: (entry) => {
113
+ if (entry.size == 0) {
114
+ compressSuccess = false
115
+ }
92
116
  }
117
+ })
118
+ if (compressSuccess == false) {
119
+ fs.unlinkSync(fileName)
120
+ retryCount++
93
121
  }
122
+ }
123
+ while ((compressSuccess == false) && (retryCount <= maxRetry))
124
+ }
94
125
 
95
- const compressIt = (fileName, folderPath) => {
96
- // tar v7.1.0 -> tested 10x times, 2-3x times last compressed file have empty content
97
- // tar v.7.4.0 -> tested 1000x times, all files have content (used app.test.js)
98
- let compressSuccess = false
99
- const maxRetry = 10
100
- let retryCount = 0
101
- do {
102
- tar.c({ file: fileName, cwd: folderPath, gzip: { level: 9 }, strict: true, sync: true }, fs.readdirSync(folderPath))
103
- compressSuccess = true
104
- tar.t({
105
- file: fileName, cwd: folderPath, sync: true, onentry: (entry) => {
106
- if (entry.size == 0) {
107
- compressSuccess = false
108
- }
109
- }
110
- })
111
- if (compressSuccess == false) {
112
- fs.unlinkSync(fileName)
113
- retryCount++
114
- }
126
+ const getNormalizedFileName = (file) => {
127
+ if (typeof file === 'string') {
128
+ return file.trim()
129
+ }
130
+ if (file && typeof file === 'object') {
131
+ const fileName = file.fileName || file.name || file.path
132
+ return (fileName || '').toString().trim()
133
+ }
134
+ return ''
135
+ }
136
+
137
+ const normalizeFileList = (files) => {
138
+ if (Array.isArray(files)) {
139
+ return files
140
+ .map((file) => getNormalizedFileName(file))
141
+ .filter((fileName) => fileName.length > 0)
142
+ }
143
+ if (typeof files === 'string') {
144
+ return files.split(',').map((f) => f.trim()).filter((f) => f.length > 0)
145
+ }
146
+ return null
147
+ }
148
+
149
+ const normalizeAppName = (value) => String(value ?? '')
150
+ .trim()
151
+ .replace(/\s+/g, '')
152
+ .toLowerCase()
153
+
154
+ const normalizeBodyScripts = (body = null) => {
155
+ const scriptMap = new Map()
156
+ if (!body || typeof body !== 'object') {
157
+ return scriptMap
158
+ }
159
+
160
+ const setScript = (name, content) => {
161
+ const fileName = (name || '').toString().trim().toLowerCase()
162
+ if (fileName.length === 0) {
163
+ return
164
+ }
165
+ if (typeof content === 'string') {
166
+ scriptMap.set(fileName, content)
167
+ } else if (Buffer.isBuffer(content)) {
168
+ scriptMap.set(fileName, content.toString())
169
+ }
170
+ }
171
+
172
+ const fromObjects = (list) => {
173
+ if (!Array.isArray(list)) {
174
+ return
175
+ }
176
+ list.forEach((item) => {
177
+ if (!item || typeof item !== 'object') {
178
+ return
115
179
  }
116
- while ((compressSuccess == false) && (retryCount <= maxRetry))
180
+ const fileName = item.fileName || item.name || item.path
181
+ const content = item.content ?? item.data ?? item.script
182
+ setScript(fileName, content)
183
+ })
184
+ }
185
+
186
+ fromObjects(body.files)
187
+ fromObjects(body.fileList)
188
+
189
+ if (body.fileContents && (typeof body.fileContents === 'object')) {
190
+ Object.entries(body.fileContents).forEach(([name, content]) => setScript(name, content))
191
+ }
192
+ if (body.scripts && (typeof body.scripts === 'object')) {
193
+ Object.entries(body.scripts).forEach(([name, content]) => setScript(name, content))
194
+ }
195
+
196
+ return scriptMap
197
+ }
198
+
199
+ const getFileList = ({ workingDir = process.cwd(), files = null } = {}) => {
200
+ const fileList = normalizeFileList(files)
201
+ const candidateFiles = fileList || fs.readdirSync(workingDir)
202
+
203
+ return candidateFiles.filter((fileName) => {
204
+ const normalizedName = fileName.toString().trim()
205
+ const filePath = path.isAbsolute(normalizedName) ? normalizedName : path.join(workingDir, normalizedName)
206
+ if (!fs.existsSync(filePath)) {
207
+ return false
117
208
  }
209
+ const stat = fs.statSync(filePath)
210
+ const baseName = path.basename(normalizedName)
211
+ return stat.isFile() && (stat.size > 0) && ((baseName.split('.').pop().toLowerCase().match(/^(js)$/) || (baseName.toLowerCase() == 'menu.json')))
212
+ })
213
+ }
118
214
 
119
- const getFileList = () => {
120
- return (fs.readdirSync(process.cwd())).filter((fileName) => {
121
- const stat = fs.statSync(fileName)
122
- return stat.isFile() && (stat.size > 0) && ((fileName.split('.').pop().toLowerCase().match(/^(js)$/) || (fileName.toLowerCase() == 'menu.json')))
123
- })
215
+ const parseApplicationConfigData = (rawConfig) => {
216
+ if (rawConfig == null) {
217
+ return null
218
+ }
219
+ if (typeof rawConfig === 'object') {
220
+ return rawConfig
221
+ }
222
+
223
+ const rawText = String(rawConfig).trim()
224
+ if (!rawText) {
225
+ return null
226
+ }
227
+
228
+ try {
229
+ return JSON.parse(rawText)
230
+ } catch {
231
+ try {
232
+ return Function(`${rawText}; return (typeof get === 'function') ? get() : null;`)()
233
+ } catch {
234
+ return null
124
235
  }
236
+ }
237
+ }
125
238
 
126
- async function runUnitTests() { //SCY BZ 4331
127
- let jestCommand = ['--no-install', 'jest', '--json'];
128
- let output = '';
239
+ const resolveAppDisplayNameFromApplicationConfig = async ({ server, apiPort, dbIndex, sub }) => {
240
+ if ((typeof axios.request) !== 'function') {
241
+ return ''
242
+ }
129
243
 
130
- const testDir = path.join(options.workingDir, "test");
131
- const workDir = path.resolve(options.workingDir);
132
- try {
133
- process.chdir(testDir)
134
- } catch (error) {
135
- if (error.code === 'ENOENT') {
136
- jestCommand.push('--passWithNoTests')
244
+ const baseUrl = 'fina/rest/TOrmMethod/%22list%22'
245
+ const url = sub
246
+ ? `${server}/hub/${baseUrl}?subdomain=${sub}`
247
+ : `${server}:${apiPort}/${baseUrl}`
248
+
249
+ try {
250
+ const response = await axios.request({
251
+ method: 'POST',
252
+ url,
253
+ headers: { 'Content-Type': 'text/plain' },
254
+ data: {
255
+ dbIndex,
256
+ object: {
257
+ columns: [
258
+ { title: 'name', data: 'SYS$CONFIG.NAME' },
259
+ { title: 'data', data: 'SYS$CONFIG.DATA' }
260
+ ]
137
261
  }
138
262
  }
139
- process.chdir(workDir);
263
+ })
140
264
 
141
- const child = process.platform === 'win32'
142
- ? spawn('cmd.exe', ['/d', '/s', '/c', 'npx', ...jestCommand])
143
- : spawn('npx', jestCommand)
144
- const collectOutput = data => {
145
- output += data?.toString() || ''
146
- }
147
- child.stderr?.on('data', collectOutput)
148
- child.stdout?.on('data', () => { }) // SCY BZ 4363, ref : https://nodejs.org/download/release/v22.19.0/docs/api/child_process.html
149
-
150
- child.on('error', e => {
151
- output += e?.message ? `\n${e.message}` : ''
152
- });
153
- child.on('close', async code => {
154
- console.log('====================');
155
- console.log(output);
156
- console.log('====================');
157
- let noTestsFound = output.includes('No tests found');
158
- let missingJest = (output.includes('missing packages') && output.includes('jest'));
159
- let passWithNoTests = jestCommand.includes('--passWithNoTests');
160
-
161
- if ((code == 0) ||
162
- ((code == 1) && noTestsFound) ||
163
- ((code == 1) && missingJest && passWithNoTests)) {
164
- await addApp()
165
- } else {
166
- console.error('Biz-A Add aborted');
265
+ const rawRows = response?.data?.data
266
+ const rows = Array.isArray(rawRows)
267
+ ? rawRows
268
+ : (() => {
269
+ try {
270
+ return JSON.parse(rawRows || '[]')
271
+ } catch {
272
+ return []
167
273
  }
168
- })
274
+ })()
275
+
276
+ const applicationConfigRow = rows.find((row) => {
277
+ const name = String(row?.['SYS$CONFIG.NAME'] ?? row?.NAME ?? row?.name ?? '').trim().toUpperCase()
278
+ return name === 'APPLICATION_CONFIG'
279
+ })
280
+ if (!applicationConfigRow) {
281
+ return ''
169
282
  }
170
283
 
171
- async function addApp() {
172
- /*
173
- hex => 2 char = 1 bytes => can be encrypted
174
- base64 => 4 char = 3 bytes => can be encrypted. Smaller size compare to Hex
175
- utf8 => 1 char = 1 - 4 bytes => can not be encrypted, encryption need precise bytes per Character. Smallest Size compare to Hex and base64
176
- */
177
- try {
178
- const bundlingStart = performance.now()
179
- const rootFolder = './upload/'
180
- const bundleName = basename(process.cwd()).trim().replace(' ', '').toLowerCase()
181
- const bundleFolder = rootFolder + bundleName + '/'
182
- const files = getFileList()
183
- if (files.length > 0) {
184
- const keys = await prepareKeys()
185
- let processedFile = 0
186
- fs.rmSync(rootFolder, { force: true, recursive: true })
187
- fs.mkdirSync(bundleFolder, { recursive: true })
188
- for (const file of files) {
189
- const fileName = file.toLowerCase();
190
- const encryptedScript = encryptScript(await prepareScript(fileName, fs.readFileSync(fileName).toString(), options.verbose), keys.encryptKey)
191
- if (fileName == 'menu.json') {
192
- keys.metadata['menu'] = encryptedScript.toString('base64')
193
- } else {
194
- fs.writeFileSync(bundleFolder + fileName, encryptedScript.toString('base64'))
195
- }
196
-
197
- processedFile++
198
- }
199
- const bundleFile = `${rootFolder}${bundleName}.tgz`
200
- compressIt(bundleFile, bundleFolder)
201
- console.log(`Finished packing ${processedFile} files into "${bundleFile}" (${((performance.now() - bundlingStart) / 1000).toFixed(2)}s)`)
202
-
203
- // send to API
204
- const uploadingStart = performance.now()
205
- const data = (fs.readFileSync(bundleFile)).toString('base64') // *.tgz to base64String
206
- const baseUrl = 'fina/rest/TOrmMethod/%22setApp%22'
207
- const url = options.sub ?
208
- `${options.server}/hub/${baseUrl}?subdomain=${options.sub}` :
209
- `${options.server}:${options.apiPort}/${baseUrl}`
210
- const headers = { 'Content-Type': 'text/plain' }
211
- const param = { _parameters: [options.dbIndex, bundleName, data, keys.metadata] }
212
- const res = await axios.post(url, param, { headers: headers });
213
- if (res.data.success) {
214
- console.log(`Finished uploading "${bundleFile}" (${((performance.now() - uploadingStart) / 1000).toFixed(2)}s)`)
215
- fs.rmSync(rootFolder, { force: true, recursive: true })
216
- } else {
217
- console.error(res.data.error)
218
- }
284
+ const applicationConfig = parseApplicationConfigData(
285
+ applicationConfigRow?.['SYS$CONFIG.DATA'] ?? applicationConfigRow?.DATA ?? applicationConfigRow?.data
286
+ )
287
+ const displayName = applicationConfig?.metadata?.name
288
+ return (typeof displayName === 'string') ? displayName.trim() : ''
289
+ } catch {
290
+ return ''
291
+ }
292
+ }
293
+
294
+ async function addApp({
295
+ workingDir = process.cwd(),
296
+ verbose = false,
297
+ server = 'http://localhost',
298
+ apiPort = 212,
299
+ dbIndex = 2,
300
+ sub,
301
+ files = null,
302
+ body = null
303
+ } = {}) {
304
+ const oldCwd = process.cwd()
305
+
306
+ /*
307
+ hex => 2 char = 1 bytes => can be encrypted
308
+ base64 => 4 char = 3 bytes => can be encrypted. Smaller size compare to Hex
309
+ utf8 => 1 char = 1 - 4 bytes => can not be encrypted, encryption need precise bytes per Character. Smallest Size compare to Hex and base64
310
+ */
311
+ try {
312
+ process.chdir(path.resolve(workingDir))
313
+
314
+ const bundlingStart = performance.now()
315
+ const rootFolder = './upload/'
316
+ const bundleName = normalizeAppName(basename(process.cwd()))
317
+ const bundleFolder = rootFolder + bundleName + '/'
318
+ const sourceFiles = getFileList({ workingDir: process.cwd(), files })
319
+ const bodyScripts = normalizeBodyScripts(body)
320
+ const requestedFiles = normalizeFileList(files)
321
+ const bodySourceFiles = Array.from(bodyScripts.keys()).filter((fileName) => {
322
+ const isSupportedFile = fileName.match(/\.js$/) || (fileName === 'menu.json')
323
+ if (!isSupportedFile) {
324
+ return false
325
+ }
326
+ if (!requestedFiles) {
327
+ return true
328
+ }
329
+ return requestedFiles.some((requested) => path.basename(requested).toLowerCase() === fileName)
330
+ })
331
+ const mergedSourceFiles = Array.from(new Set([
332
+ ...sourceFiles,
333
+ ...bodySourceFiles
334
+ ]))
335
+
336
+ if (mergedSourceFiles.length > 0) {
337
+ const keys = await prepareKeys()
338
+ if (!keys) {
339
+ const msg = 'Can not prepare encryption keys'
340
+ console.error(msg)
341
+ return { success: false, error: msg }
342
+ }
343
+
344
+ let processedFile = 0
345
+ fs.rmSync(rootFolder, { force: true, recursive: true })
346
+ fs.mkdirSync(bundleFolder, { recursive: true })
347
+
348
+ for (const sourceFile of mergedSourceFiles) {
349
+ const sourceFilePath = path.isAbsolute(sourceFile) ? sourceFile : path.resolve(process.cwd(), sourceFile)
350
+ const fileName = path.basename(sourceFile).toLowerCase()
351
+ const scriptSource = bodyScripts.has(fileName)
352
+ ? bodyScripts.get(fileName)
353
+ : fs.readFileSync(sourceFilePath).toString()
354
+ const preparedScript = await prepareScript(fileName, scriptSource, verbose)
355
+ const encryptedScript = encryptScript(preparedScript, keys.encryptKey)
356
+ if (fileName == 'menu.json') {
357
+ keys.metadata['menu'] = encryptedScript.toString('base64')
219
358
  } else {
220
- console.error('Nothing to upload. Please recheck your app folder.')
359
+ fs.writeFileSync(bundleFolder + fileName, encryptedScript.toString('base64'))
221
360
  }
222
- } catch (e) {
223
- console.error(e.response?.data ? e.response.data : e)
361
+
362
+ processedFile++
224
363
  }
364
+ const bundleFile = `${rootFolder}${bundleName}.tgz`
365
+ compressIt(bundleFile, bundleFolder)
366
+ console.log(`Finished packing ${processedFile} files into "${bundleFile}" (${((performance.now() - bundlingStart) / 1000).toFixed(2)}s)`)
367
+
368
+ // send to API
369
+ const uploadingStart = performance.now()
370
+ const data = (fs.readFileSync(bundleFile)).toString('base64') // *.tgz to base64String
371
+ const baseUrl = 'fina/rest/TOrmMethod/%22setApp%22'
372
+ const url = sub ?
373
+ `${server}/hub/${baseUrl}?subdomain=${sub}` :
374
+ `${server}:${apiPort}/${baseUrl}`
375
+ const headers = { 'Content-Type': 'text/plain' }
376
+ const appDisplayName = await resolveAppDisplayNameFromApplicationConfig({ server, apiPort, dbIndex, sub })
377
+ if (appDisplayName.length > 0) {
378
+ keys.metadata['name'] = appDisplayName
379
+ }
380
+ const uploadAppName = normalizeAppName(appDisplayName || bundleName)
381
+ const param = { _parameters: [dbIndex, uploadAppName, data, keys.metadata] }
382
+ const res = await axios.post(url, param, { headers: headers })
383
+ if (res.data.success) {
384
+ console.log(`Finished uploading "${bundleFile}" (${((performance.now() - uploadingStart) / 1000).toFixed(2)}s)`)
385
+ fs.rmSync(rootFolder, { force: true, recursive: true })
386
+ return { success: true, data: res.data }
387
+ }
388
+
389
+ console.error(res.data.error)
390
+ return { success: false, error: res.data.error, data: res.data }
391
+ }
392
+
393
+ const msg = 'Nothing to upload. Please recheck your app folder.'
394
+ console.error(msg)
395
+ return { success: false, error: msg }
396
+ } catch (e) {
397
+ const errMsg = (e.response?.data ? e.response.data : e)
398
+ console.error(errMsg)
399
+ return { success: false, error: errMsg }
400
+ } finally {
401
+ process.chdir(oldCwd)
402
+ }
403
+ }
404
+
405
+ async function runUnitTests(options) { //SCY BZ 4331
406
+ return new Promise((resolve) => {
407
+ let jestCommand = ['--no-install', 'jest', '--json']
408
+ let output = ''
409
+
410
+ const testDir = path.join(options.workingDir, 'test')
411
+ const workDir = path.resolve(options.workingDir)
412
+ try {
413
+ process.chdir(testDir)
414
+ } catch (error) {
415
+ if (error.code === 'ENOENT') {
416
+ jestCommand.push('--passWithNoTests')
417
+ }
418
+ }
419
+ process.chdir(workDir)
420
+
421
+ const child = process.platform === 'win32'
422
+ ? spawn('cmd.exe', ['/d', '/s', '/c', 'npx', ...jestCommand])
423
+ : spawn('npx', jestCommand)
424
+ const collectOutput = data => {
425
+ output += data?.toString() || ''
225
426
  }
427
+ child.stderr?.on('data', collectOutput)
428
+ child.stdout?.on('data', () => { }) // SCY BZ 4363, ref : https://nodejs.org/download/release/v22.19.0/docs/api/child_process.html
429
+
430
+ child.on('error', e => {
431
+ output += e?.message ? `\n${e.message}` : ''
432
+ })
433
+
434
+ child.on('close', async code => {
435
+ console.log('====================')
436
+ console.log(output)
437
+ console.log('====================')
438
+ const noTestsFound = output.includes('No tests found')
439
+ const missingJest = (output.includes('missing packages') && output.includes('jest'))
440
+ const passWithNoTests = jestCommand.includes('--passWithNoTests')
441
+
442
+ if ((code == 0) ||
443
+ ((code == 1) && noTestsFound) ||
444
+ ((code == 1) && missingJest && passWithNoTests)) {
445
+ await addApp({
446
+ workingDir: workDir,
447
+ verbose: options.verbose,
448
+ server: options.server,
449
+ apiPort: options.apiPort,
450
+ dbIndex: options.dbIndex,
451
+ sub: options.sub,
452
+ files: options.files
453
+ })
454
+ } else {
455
+ console.error('Biz-A Add aborted')
456
+ }
457
+ resolve()
458
+ })
459
+ })
460
+ }
226
461
 
227
- await runUnitTests()
462
+ const buildCli = () => Object
463
+ .keys(options)
464
+ .reduce((app, optKey) => app = app.option(optKey, options[optKey]), yargs(process.argv.slice(2)))
465
+ .command('add', 'Add Biz-A Application', addCommandOptions, async (commandOptions) => {
466
+ await runUnitTests(commandOptions)
228
467
  })
229
- .command('remove', 'Remove Biz-A Application', removeCommandOptions, (options) => {
230
- (async () => {
468
+ .command('remove', 'Remove Biz-A Application', removeCommandOptions, (commandOptions) => {
469
+ ; (async () => {
231
470
  try {
232
471
  const baseUrl = 'fina/rest/TOrmMethod/%22deleteApp%22'
233
- const url = options.sub ?
234
- `${options.server}/hub/${baseUrl}?subdomain=${options.sub}` :
235
- `${options.server}:${options.apiPort}/${baseUrl}`
472
+ const url = commandOptions.sub ?
473
+ `${commandOptions.server}/hub/${baseUrl}?subdomain=${commandOptions.sub}` :
474
+ `${commandOptions.server}:${commandOptions.apiPort}/${baseUrl}`
236
475
  const headers = { 'Content-Type': 'text/plain' }
237
- const deleteApps = options.appName.trim().replaceAll(' ', '').toLowerCase()
238
- const param = { _parameters: [options.dbIndex, deleteApps] }
239
- const res = await axios.post(url, param, { headers: headers });
476
+ const deleteApps = commandOptions.appName.trim().replaceAll(' ', '').toLowerCase()
477
+ const param = { _parameters: [commandOptions.dbIndex, deleteApps] }
478
+ const res = await axios.post(url, param, { headers: headers })
240
479
  if (res.data?.success) {
241
480
  if (deleteApps == '') {
242
481
  console.log('All apps removed')
@@ -261,8 +500,9 @@ Object
261
500
  .recommendCommands()
262
501
  .demandCommand(1, 'You need at least one command before moving on')
263
502
  .strict()
264
- .parse();
265
-
266
- export { options, addCommandOptions, removeCommandOptions }
267
503
 
504
+ if (process.env.BIZA_APP_SKIP_PARSE !== '1') {
505
+ buildCli().parse()
506
+ }
268
507
 
508
+ export { options, addCommandOptions, removeCommandOptions, getFileList, addApp }
@@ -1,4 +1,4 @@
1
- import { apiRequestListener, RECONNECT_SOCKET_DELAY } from './hubEvent.js'
1
+ import { clientListener, RECONNECT_SOCKET_DELAY } from './hubEvent.js'
2
2
  import { deploymentListenerForVSCode } from './deployEvent.js'
3
3
  import { Tunnel as QuickTunnel } from 'cloudflared'
4
4
  import { Server as ioServer } from 'socket.io'
@@ -61,7 +61,7 @@ export function directHubEvent(serverSocket, argv){
61
61
 
62
62
  if (clientSocket.handshake.query.isClient) {
63
63
  setConnectListeners(clientSocket);
64
- apiRequestListener(clientSocket, argv);
64
+ clientListener(clientSocket, argv);
65
65
  clientSocket.join(CLIENT_ROOM);
66
66
  }
67
67
  else if (clientSocket.handshake.query.isDeploy) {
package/bin/hub.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  if (!process.env.NODE_ENV) {
4
4
  process.env.NODE_ENV = 'production';
@@ -6,9 +6,9 @@ if (!process.env.NODE_ENV) {
6
6
 
7
7
  import yargs from 'yargs';
8
8
  import { io as ioClient } from "socket.io-client";
9
- import { streamEvent, hubEvent, RECONNECT_SOCKET_DELAY } from './hubEvent.js'
9
+ import { streamEvent, hubEvent, RECONNECT_SOCKET_DELAY, status } from './hubEvent.js'
10
10
  import { createLogger, transports, format } from "winston";
11
- import os from 'node:os'
11
+ import os from 'os';
12
12
  import { directHubEvent, localhostTunnel, createSocketServer }from './directHubEvent.js'
13
13
  import { env } from "../envs/env.js"
14
14
 
@@ -138,7 +138,7 @@ app.use('/cb', (req, res)=>{
138
138
  });
139
139
 
140
140
  app.use('/status', (req, res)=>{
141
- res.status(200).json(argv.cliAddress());
141
+ res.status(200).json(status(argv));
142
142
  });
143
143
 
144
144
  // create HTTP(s) Server
@@ -150,8 +150,8 @@ const getProtocol = ()=>(isHttps ? 'Https://' : 'Http://')
150
150
  let server = isHttps ? https.createServer({key: fs.readFileSync(keyFile), cert: fs.readFileSync(certFile), ca: fs.readFileSync(rootFile),}, app) : http.createServer(app)
151
151
  argv.cliAddress = ()=>{
152
152
  const ip = Object.values(os.networkInterfaces()).flat().reduce((ip, {family, address, internal})=> ip || !internal && family==='IPv4' && address, undefined)
153
- return {ip, port: argv.serverport, address: `${ip}:${argv.serverport}`, publicUrl: argv.connectedPublicUrl, hubUrl: argv.connectedHubUrl}
154
- }
153
+ return {ip, port: argv.serverport, address: `${ip}:${argv.serverport}`, publicUrl: argv.connectedPublicUrl, hubUrl: argv.connectedHubUrl};
154
+ };
155
155
  server.listen(argv.serverport, () => {
156
156
  const info = argv.cliAddress()
157
157
  console.log(`${new Date()}: CLI is listening at ${getProtocol() + (process.env.HOST || info.ip || 'localhost')}:${info.port} `);