biz-a-cli 2.3.15 → 2.3.17

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 ADDED
@@ -0,0 +1,313 @@
1
+ #!/usr/bin/env node
2
+
3
+ import yargs from "yargs"
4
+ import axios from "axios"
5
+ import fs from "fs"
6
+ import { runInNewContext } from "vm"
7
+ import uglify from "uglify-js"
8
+ import * as tar from "tar"
9
+ import { verify, sign, privateDecrypt, constants as cryptoConstants, randomBytes, createCipheriv } from "node:crypto"
10
+ import { basename } from "node:path"
11
+ import { env } from "../envs/env.js"
12
+ import * as vm from 'node:vm'
13
+
14
+ const keyFolderPath = process.argv[1].substring(0, process.argv[1].lastIndexOf("\\bin")) + "\\key"
15
+
16
+ yargs(process.argv.slice(2))
17
+ .option("s", {
18
+ alias: "server",
19
+ describe: "Server URL (ex: https://biz-a.herokuapp.com or http://192.168.1.1 or https://finaapi.imamatek.com)",
20
+ type: "string",
21
+ demandOption: true
22
+ })
23
+ .option("i", {
24
+ alias: "dbIndex",
25
+ describe: "database index",
26
+ type: "number",
27
+ demandOption: true
28
+ })
29
+ .option("sub", {
30
+ alias: "subdomain",
31
+ describe: "Subdomain",
32
+ type: "string",
33
+ demandOption: false
34
+ })
35
+ .option("p", {
36
+ alias: "apiPort",
37
+ describe: "FINA API Port",
38
+ type: "string",
39
+ demandOption: false,
40
+ default : "212"
41
+ })
42
+ .command('add', 'Add Biz-A Application',
43
+ {
44
+ 'd': {
45
+ alias: "workingDir",
46
+ describe: "Path to templates directory",
47
+ type: "string",
48
+ demandOption: false,
49
+ default: process.cwd(),
50
+ },
51
+ 'v': {
52
+ alias: "verbose",
53
+ describe: "Print info to console",
54
+ type: "boolean",
55
+ demandOption: false,
56
+ default: false,
57
+ }
58
+ },
59
+ async (options)=>{
60
+
61
+ function printInfo(msg){if (options.verbose) {console.log(msg)}}
62
+
63
+ const prepareKeys = async ()=>{
64
+ const data = Buffer.from(JSON.stringify({issuer: 'CLI', acquirer: 'Client'})).toString('base64')
65
+ const privateKey = fs.readFileSync(`${keyFolderPath}\\cliPrivate.pem`)
66
+ const signature = sign('sha256', data, {key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_PSS_PADDING}).toString('base64')
67
+ const res = await axios.get(env.BIZA_SERVER_LINK+'/api/issuerKey', {params: {data, signature}})
68
+ 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'))) {
69
+ const resData = JSON.parse(Buffer.from(res.data.data, 'base64').toString())
70
+ const decryptedAESKey = privateDecrypt({key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_OAEP_PADDING}, Buffer.from(resData.issuer.key, 'base64')).toString()
71
+ const cliSignature = (signedData)=>sign('sha256', signedData, {key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_PSS_PADDING}).toString('base64')
72
+ const acquirerData = Buffer.from(JSON.stringify(resData.acquirer)).toString('base64')
73
+ const signature = cliSignature(acquirerData)
74
+ return {encryptKey: decryptedAESKey, metadata: {acquirer: {data: acquirerData, signature}}}
75
+ }
76
+ else {
77
+ return null
78
+ }
79
+ }
80
+
81
+ function replacer(key, value) {
82
+ /*
83
+ // with line break
84
+ if (typeof value == 'function') {
85
+ let arr = value.toString().replace(/(\r\n|\n|\r)/gm, "°").split("°");
86
+ if (arr.length < 3) throw 'Function must be minimal 3 lines';
87
+ return [
88
+ 'window.Function',
89
+ getParam(arr[0]),
90
+ arr.slice(1, arr.length - 1)
91
+ ];
92
+ } else {
93
+ return value;
94
+ }
95
+ */
96
+
97
+ // without line break
98
+ if (typeof value == 'function') {
99
+ const fnScript = value.toString()
100
+ let params, body = ''
101
+ if (fnScript.indexOf('{')==-1) {
102
+ const arr = fnScript.split('=>')
103
+ params = arr[0]
104
+ body = arr.slice(1).join()
105
+ } else {
106
+ params = fnScript.split('{')[0]
107
+ body = fnScript.substring(fnScript.indexOf('{')+1, fnScript.lastIndexOf('}'))
108
+ }
109
+ params = params.replace(/(\s|=>|\(|\)|function)/gim, '')
110
+ printInfo(['window.Function', params, body])
111
+ return ['window.Function', params, body]
112
+ } else {
113
+ return value;
114
+ }
115
+ }
116
+
117
+ const minifiedIt = async (fileName) => {
118
+ const dataFile = fs.readFileSync(fileName);
119
+ printInfo(`===================\n${fileName.toUpperCase()}\n===================`)
120
+
121
+ const minifyResult = uglify.minify(dataFile.toString(), {compress: false, mangle: false})
122
+ if (minifyResult.error ) {throw (`${fileName} : ` + minifyResult.error)}
123
+ let jsMinData = minifyResult.code
124
+ jsMinData = jsMinData.replace('module.exports=get;', '') // Ex : at "executeBlockLib.js" of Imamatek App, will throw error. We need this line code for testing or debugging
125
+ const minifiedBuffer = Buffer.from(jsMinData)
126
+ printInfo(['Minify : \n', minifiedBuffer.toString()].join(''))
127
+
128
+ let stringifyData = ''
129
+ const isJsonFile = fileName.split('.').pop().toLowerCase().match(/^(json)$/)
130
+
131
+ // compile using node.js VM
132
+ try {
133
+ printInfo('Running script with VM')
134
+ if (isJsonFile) {
135
+ const vmResult = runInNewContext(minifiedBuffer.toString())
136
+ stringifyData = vmResult ? JSON.stringify((typeof vmResult=='function') ? vmResult() : (vmResult || ''), replacer) : ''
137
+ } else {
138
+ // for handling "get" function in local scope (let, var, const)
139
+ const sandbox = vm.createContext({script: ''});
140
+ vm.runInContext(
141
+ minifiedBuffer.toString() + "\n script = (typeof get=='undefined') ? '' : (typeof get=='function') ? get() : (get||'')",
142
+ sandbox
143
+ )
144
+ stringifyData = JSON.stringify(sandbox.script, replacer)
145
+ }
146
+ } catch (err){
147
+ printInfo(`${fileName} : ` + err) // Do not log it as error, we will try to compile the script using ES6 Import
148
+ stringifyData = ''
149
+ }
150
+
151
+ const isEmptyScript = (scriptText)=>((typeof scriptText=='undefined') || (scriptText===null) || (scriptText.trim()==='""') || (typeof scriptText=='string' && scriptText.trim().length==0))
152
+
153
+ // compile using ES6 Import
154
+ if (isEmptyScript(stringifyData)) {
155
+ try {
156
+ printInfo('Running script with Import function')
157
+ const lib = await import(`file://${process.cwd()}\\${fileName}`, isJsonFile ? {assert: {type: "json"}} : {})
158
+ stringifyData = JSON.stringify((typeof lib.default=='function') ? lib.default() : (lib.default || ''), replacer).replace(/ /g,'') // remove trailing whitespace
159
+ // stringifyData = stringifyData.replace(/\\r\\n/g,'') // remove line break. We can not do this because "inline css" still requires line breaks
160
+ } catch (error){
161
+ throw(`${fileName} : ` + error)
162
+ }
163
+ }
164
+
165
+ if (isEmptyScript(stringifyData)) {
166
+ throw(`${fileName} : ` + 'Failed to compile template script.\nPlease make sure the script is correct and not returning empty result')
167
+ } else {
168
+ printInfo('Array function :')
169
+ printInfo(['Stringify : \n', stringifyData].join(''))
170
+ }
171
+ // console.log('RESULT =>', stringifyData)
172
+ printInfo('===================')
173
+ return Buffer.from(stringifyData)
174
+ }
175
+
176
+ const encryptIt = (data, encryptKey)=>{
177
+ const initializeVector = randomBytes(16) // "iv" is unique for each template file
178
+ const cipher = createCipheriv('aes-256-cbc', Buffer.from(encryptKey, 'base64'), initializeVector)
179
+ return Buffer.concat([initializeVector, cipher.update(data), cipher.final()]) // we put "iv" at beginning of cipherText, seperate it when doing decryption
180
+ }
181
+
182
+ const compressIt = (fileName, folderPath)=>{
183
+ // tar v7.1.0 -> tested 10x times, 2-3x times last compressed file have empty content
184
+ // tar v.7.4.0 -> tested 1000x times, all files have content (used app.test.js)
185
+ let compressSuccess = false
186
+ const maxRetry = 10
187
+ let retryCount = 0
188
+ do {
189
+ tar.c({file: fileName, cwd: folderPath, gzip: {level:9}, strict: true, sync: true}, fs.readdirSync(folderPath))
190
+ compressSuccess = true
191
+ tar.t({file: fileName, cwd: folderPath, sync: true, onentry: (entry)=>{
192
+ if (entry.size==0) {
193
+ compressSuccess = false
194
+ }
195
+ }})
196
+ if (compressSuccess==false) {
197
+ fs.unlinkSync(fileName)
198
+ retryCount++
199
+ }
200
+ }
201
+ while ((compressSuccess==false) && (retryCount<=maxRetry))
202
+ }
203
+
204
+ const getFileList = ()=>{
205
+ return (fs.readdirSync(process.cwd())).filter((fileName)=>{
206
+ const stat = fs.statSync(fileName)
207
+ return stat.isFile() && (stat.size>0) && ((fileName.split('.').pop().toLowerCase().match(/^(js)$/) || (fileName.toLowerCase()=='menu.json')))
208
+ })
209
+ }
210
+
211
+ async function addApp() {
212
+ /*
213
+ hex => 2 char = 1 bytes => can be encrypted
214
+ base64 => 4 char = 3 bytes => can be encrypted. Smaller size compare to Hex
215
+ utf8 => 1 char = 1 - 4 bytes => can not be encrypted, encryption need precise bytes per Character. Smallest Size compare to Hex and base64
216
+ */
217
+ process.chdir(options.workingDir)
218
+ try {
219
+ const bundlingStart = performance.now()
220
+ const rootFolder = './upload/'
221
+ const bundleName = basename(process.cwd()).trim().replace(' ', '').toLowerCase()
222
+ const bundleFolder = rootFolder+bundleName+'/'
223
+ const files = getFileList()
224
+ if (files.length>0) {
225
+ const keys = await prepareKeys()
226
+ let processedFile = 0
227
+ fs.rmSync(rootFolder, {force: true, recursive: true})
228
+ fs.mkdirSync(bundleFolder, {recursive: true})
229
+ for (const file of files) {
230
+ const fileName = file.toLowerCase();
231
+ // if (['finalib.js', 'solib.js'].indexOf(fileName)==-1) {continue}
232
+ const minifiedData = await minifiedIt(fileName);
233
+ const encryptedData = encryptIt(minifiedData, keys.encryptKey)
234
+ if (fileName=='menu.json') {
235
+ keys.metadata['menu'] = encryptedData.toString('base64')
236
+ } else {
237
+ fs.writeFileSync(bundleFolder+fileName, encryptedData.toString('base64'))
238
+ }
239
+ processedFile++
240
+ }
241
+ const bundleFile = `${rootFolder}${bundleName}.tgz`
242
+ compressIt(bundleFile, bundleFolder)
243
+ console.log(`Finished packing ${processedFile} files into "${bundleFile}" (${((performance.now()-bundlingStart)/1000).toFixed(2)}s)`)
244
+
245
+ // send to FINAPI
246
+ const uploadingStart = performance.now()
247
+ const data = (fs.readFileSync(bundleFile)).toString('base64') // *.tgz to base64String
248
+ const baseUrl = 'fina/rest/TOrmMethod/%22setApp%22'
249
+ const url = options.sub ?
250
+ `${options.server}/hub/${baseUrl}?subdomain=${options.sub}` :
251
+ `${options.server}:${options.apiPort}/${baseUrl}`
252
+ const headers = {'Content-Type': 'text/plain'}
253
+ const param = { _parameters: [options.dbIndex, bundleName, data, keys.metadata] }
254
+ const res = await axios.post(url, param, { headers: headers });
255
+ if (res.data.success) {
256
+ console.log(`Finished uploading "${bundleFile}" (${((performance.now()-uploadingStart)/1000).toFixed(2)}s)`)
257
+ fs.rmSync(rootFolder, {force: true, recursive: true})
258
+ } else {
259
+ console.error(res.data.error)
260
+ }
261
+ }
262
+ } catch (e) {
263
+ console.error(e.response?.data ? e.response.data : e)
264
+ }
265
+ }
266
+
267
+ await addApp()
268
+ }
269
+ )
270
+ .command('remove', 'Remove Biz-A Application',
271
+ {
272
+ 'n': {
273
+ alias: "appName",
274
+ describe: "Application name",
275
+ type: "string",
276
+ demandOption: true,
277
+ default: ""
278
+ }
279
+ },
280
+ (options)=>{
281
+ (async () => {
282
+ try {
283
+ const baseUrl = 'fina/rest/TOrmMethod/%22deleteApp%22'
284
+ const url = options.sub ?
285
+ `${options.server}/hub/${baseUrl}?subdomain=${options.sub}` :
286
+ `${options.server}:${options.apiPort}/${baseUrl}`
287
+ const headers = {'Content-Type': 'text/plain'}
288
+ const deleteApps = options.appName.trim().replaceAll(' ', '').toLowerCase()
289
+ const param = { _parameters: [options.dbIndex, deleteApps] }
290
+ const res = await axios.post(url, param, { headers: headers });
291
+ if (res.data?.success) {
292
+ if (deleteApps=='') {
293
+ console.log('All apps removed')
294
+ } else {
295
+ const failedList = (res.data._f && (typeof res.data._f=='string')) ? res.data._f.trim().replaceAll(' ', '').toLowerCase().split(',') : []
296
+ const removeList = deleteApps.split(',')
297
+ removeList.forEach((app)=>{
298
+ console.log(`${app} ${failedList.indexOf(app)==-1 ? 'removed' : 'not found'}`)
299
+ })
300
+ }
301
+ } else {
302
+ console.error(res.data.error)
303
+ }
304
+ return res
305
+ } catch (e) {
306
+ const errMsg = (e.response?.data ? e.response.data : e)
307
+ console.error(errMsg)
308
+ return errMsg
309
+ }
310
+ })()
311
+ }
312
+ )
313
+ .parse();
package/bin/hub.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import yargs from 'yargs';
4
4
  import { io as ioc } from "socket.io-client";
package/bin/hubEvent.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Axios } from 'axios-observable';
1
+ import axios from 'axios';
2
2
  import net from 'node:net';
3
3
  import { createRequire } from "module";
4
4
  const require = createRequire(import.meta.url);
@@ -57,16 +57,17 @@ export default async (socket, argv) => new Promise((resolve, reject) => {
57
57
  s.end();
58
58
  });
59
59
  }
60
- const cliReqCb = (data, callback) => {
60
+ const cliReqCb = async (data, callback) => {
61
61
  const { path, method, ...remainData } = data;
62
62
 
63
- Axios.request({
63
+ const result = await axios.request({
64
64
  method: data.method,
65
65
  url: `${process.env.HOST || 'http://localhost'}:${argv.serverport}/cb${path || ''}`,
66
66
  data: remainData
67
- }).subscribe(result => callback(result.data));
67
+ })
68
+ callback(result.data);
68
69
  }
69
70
  socket.on('connect', connectCb);
70
71
  socket.on('incomingClient', incomingHubCb)
71
- socket.on('cli-req', cliReqCb );
72
+ socket.on('cli-req', cliReqCb);
72
73
  })
package/bin/proxy.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import express from 'express';
4
4
  import cors from 'cors';
package/bin/watcher.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import express from 'express';
4
4
  import cors from 'cors';
@@ -10,7 +10,7 @@ export function getInputScript(req) {
10
10
  path: req.path,
11
11
  query: req.body.query,
12
12
  headers: req.body.headers,
13
- socket : req.app.settings.socket,
13
+ socket: req.app.settings.socket,
14
14
  },
15
15
  selectedConfig: {
16
16
  url: `http://${args.hostname}:${args.port}`,
@@ -21,22 +21,19 @@ export function getInputScript(req) {
21
21
  }
22
22
 
23
23
  export async function runCliScript(req, res) {
24
-
25
- const { data, selectedConfig } = getInputScript(req);
26
- loadCliScript(selectedConfig, req.body.query).subscribe({
27
- next: async script => {
28
- let functions = extractFunctionScript(script);
29
- if (functions) {
30
- let respon = await functions.onInit(data);
31
- res.send(respon);
32
- console.log(`Run Callback Successfully!`);
33
- } else {
34
- res.send('CLI Script does not exist.');
35
- }
36
- },
37
- error: error => {
38
- console.error(error);
39
- throw new Error(error);
24
+ try {
25
+ const { data, selectedConfig } = getInputScript(req);
26
+ let script = await loadCliScript(selectedConfig, req.body.query);
27
+ let functions = extractFunctionScript(script);
28
+ if (functions) {
29
+ let respon = await functions.onInit(data);
30
+ res.send(respon);
31
+ console.log(`Run Callback Successfully!`);
32
+ } else {
33
+ res.send('CLI Script does not exist.');
40
34
  }
41
- })
35
+ } catch (error) {
36
+ console.error(error);
37
+ throw new Error(error);
38
+ }
42
39
  }
package/envs/env.dev.js CHANGED
@@ -5,7 +5,8 @@ export const envDev = {
5
5
  CDM_MONGO_LINK: 'mongodb+srv://imm_cdm:' +
6
6
  encodeURIComponent('imm@2019') + '@imm-cdm-dev.rf6wr.mongodb.net/?retryWrites=true&w=majority',
7
7
  COMPANY_REGISTER: 'companyregister-dev',
8
- BIZA_SERVER_LINK: 'https://biz-a-dev-41e7c93a25e5.herokuapp.com'
8
+ // BIZA_SERVER_LINK: 'https://biz-a-dev-41e7c93a25e5.herokuapp.com'
9
9
  // BIZA_SERVER_LINK: 'https://www.ptimf.id/biz-a-dev.com'
10
10
  // BIZA_SERVER_LINK: 'http://localhost:3000'
11
+ BIZA_SERVER_LINK: 'http://59.60.1.22:3000'
11
12
  };
package/envs/env.js CHANGED
@@ -5,5 +5,6 @@ export const env = {
5
5
  CDM_MONGO_LINK: 'mongodb+srv://imm_cdm:' +
6
6
  encodeURIComponent('imm@2019') + '@imm-cdm.ohcqt.mongodb.net/?retryWrites=true&w=majority',
7
7
  COMPANY_REGISTER: 'companyregister',
8
- BIZA_SERVER_LINK: 'https://biz-a.herokuapp.com'
8
+ // BIZA_SERVER_LINK: 'https://biz-a.herokuapp.com'
9
+ BIZA_SERVER_LINK: 'http://localhost:3000'
9
10
  };
package/mailController.js CHANGED
@@ -15,12 +15,12 @@ export function sendMailWatcher(req) {
15
15
  console.log(err.message);
16
16
  return err.message;
17
17
  } else {
18
- console.log(info);
18
+ console.log('Info:' + info);
19
19
  return info;
20
20
  }
21
21
  })
22
22
  } catch (err) {
23
- console.log(err.message);
23
+ console.log('Error: ' + err.message);
24
24
  return err.message;
25
25
  }
26
26
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "biz-a-cli",
3
3
  "nameDev": "biz-a-cli-dev",
4
- "version": "2.3.15",
5
- "versionDev": "0.0.29",
4
+ "version": "2.3.17",
5
+ "versionDev": "0.0.30",
6
6
  "description": "",
7
7
  "main": "bin/index.js",
8
8
  "type": "module",
@@ -24,11 +24,11 @@
24
24
  "hub": "bin/hub.js",
25
25
  "watcher": "bin/watcher.js",
26
26
  "uploadapp": "bin/uploadApp.js",
27
- "deleteapp": "bin/deleteApp.js"
27
+ "deleteapp": "bin/deleteApp.js",
28
+ "biza": "bin/app.js"
28
29
  },
29
30
  "dependencies": {
30
31
  "axios": "^1.6.8",
31
- "axios-observable": "^2.0.0",
32
32
  "cors": "^2.8.5",
33
33
  "dayjs": "^1.11.10",
34
34
  "express": "^4.18.3",
@@ -37,7 +37,7 @@
37
37
  "nodemailer": "^6.9.12",
38
38
  "socket.io-client": "^4.7.5",
39
39
  "socket.io-stream": "^0.9.1",
40
- "tar": "^7.1.0",
40
+ "tar": "^7.4.0",
41
41
  "uglify-js": "^3.17.4",
42
42
  "web-push": "^3.6.7",
43
43
  "winston": "^3.13.0",
@@ -48,6 +48,11 @@
48
48
  "socket.io": "^4.7.5"
49
49
  },
50
50
  "jest": {
51
- "transform": {}
51
+ "transform": {},
52
+ "testMatch": [
53
+ "<rootDir>/tests/**",
54
+ "!<rootDir>/tests/mockData",
55
+ "!<rootDir>/tests/mockData/**"
56
+ ]
52
57
  }
53
58
  }
package/readme.md ADDED
@@ -0,0 +1,60 @@
1
+ This package is used to setup applications within your Biz-A Platform.
2
+
3
+ I. Add BizA App
4
+
5
+ a. Using Static IP
6
+
7
+ BizA Add -s [IP of FINA API] -p [default: 212] -i [dbindex] -d [full or relative path to app folder]
8
+
9
+ Example :
10
+ BizA add -s http://192.168.1.1 -i 2 -d “c:\biza templates\imamatek”
11
+ BizA Add -s http://192.168.1.1 -p 1205 -i 2 -d “c:\biza templates\imamatek”
12
+
13
+ b. Using BizA Hub
14
+
15
+ BizA Add -s [BizA Hub Server] -i [dbindex] --sub [subdomain of BizA Hub Server] -d [full or relative path to app folder]
16
+
17
+ Example :
18
+ BizA Add -s https://biz-a.hub.com -i 2 –-sub imm -d “c:\biza templates\imamatek”
19
+
20
+ c. Using Domain / Sub Domain
21
+
22
+ BizA Add -s [Domain] -i [dbindex] -d [full or relative path to app folder]
23
+
24
+ Example:
25
+ BizA Add-s https://my.domain.com -i 2 -d “c:\biza templates\imamatek”
26
+
27
+ To get list of full argument, please type
28
+
29
+ BizA Add --help
30
+
31
+
32
+
33
+ II. Remove BizA App
34
+
35
+ a. Using Static IP
36
+
37
+ BizA remove -s [IP of FINA API] -p [default: 212] -i [dbindex] -n [appname | multiple appname]
38
+
39
+ Example :
40
+ BizA Remove -s http://192.168.1.1 -i 2
41
+ BizA Remove -s http://192.168.1.1 -p 1205 -i 2 -n “imamatek”
42
+ BizA Remove -s http://192.168.1.1 -p 1205 -i 2 -n “imamatek, spos”
43
+
44
+ b. Using BizA Hub
45
+
46
+ BizA Remove -s [BizA Hub Server] -i [dbindex] --sub [subdomain of BizA Hub Server] -n [appname | multiple appname]
47
+
48
+ Example :
49
+ BizA Remove -s https://biz-a.hub.com -i 2 –sub imm -n “imamatek”
50
+
51
+ c. Using Domain / Sub Domain
52
+
53
+ BizA Remove -s [Domain] -i [dbindex] -n [appname | multiple appname]
54
+
55
+ Example:
56
+ BizA Remove -s https://my.domain.com -i 2 -n “imamatek”
57
+
58
+ To get list of full argument, please type
59
+
60
+ BizA Add --help