biz-a-cli 2.3.56 → 2.3.58
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 +182 -283
- package/bin/deployEvent.js +105 -0
- package/bin/directHubEvent.js +46 -25
- package/bin/hub.js +5 -6
- package/bin/hubEvent.js +35 -7
- package/bin/script.js +125 -0
- package/bin/uploadApp.js +1 -1
- package/package.json +8 -5
- package/tests/app.test.js +92 -28
- package/tests/deployment.test.js +350 -0
- package/tests/hub.test.js +8 -7
package/bin/app.js
CHANGED
|
@@ -3,318 +3,217 @@
|
|
|
3
3
|
import yargs from "yargs"
|
|
4
4
|
import axios from "axios"
|
|
5
5
|
import fs from "fs"
|
|
6
|
-
import { runInNewContext } from "vm"
|
|
7
|
-
import uglify from "uglify-js"
|
|
8
6
|
import * as tar from "tar"
|
|
9
7
|
import { verify, sign, privateDecrypt, constants as cryptoConstants, randomBytes, createCipheriv } from "node:crypto"
|
|
10
8
|
import { basename } from "node:path"
|
|
11
9
|
import { env } from "../envs/env.js"
|
|
12
|
-
import
|
|
10
|
+
import { prepareScript, encryptScript } from "./script.js"
|
|
13
11
|
|
|
14
12
|
const keyFolderPath = process.argv[1].substring(0, process.argv[1].lastIndexOf("\\bin")) + "\\key"
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.option("i", {
|
|
24
|
-
alias: "dbIndex",
|
|
25
|
-
default: 2,
|
|
26
|
-
describe: "database index",
|
|
27
|
-
type: "number",
|
|
28
|
-
demandOption: false
|
|
29
|
-
})
|
|
30
|
-
.option("sub", {
|
|
31
|
-
alias: "subdomain",
|
|
32
|
-
describe: "Subdomain",
|
|
33
|
-
type: "string",
|
|
34
|
-
demandOption: false
|
|
35
|
-
})
|
|
36
|
-
.option("p", {
|
|
37
|
-
alias: "apiPort",
|
|
38
|
-
default : 212,
|
|
39
|
-
describe: "FINA API Port",
|
|
40
|
-
type: "number",
|
|
41
|
-
demandOption: false,
|
|
42
|
-
})
|
|
43
|
-
.command('add', 'Add Biz-A Application',
|
|
44
|
-
{
|
|
45
|
-
'd': {
|
|
46
|
-
alias: "workingDir",
|
|
47
|
-
describe: "Path to templates directory",
|
|
48
|
-
type: "string",
|
|
49
|
-
demandOption: false,
|
|
50
|
-
default: process.cwd(),
|
|
51
|
-
},
|
|
52
|
-
'v': {
|
|
53
|
-
alias: "verbose",
|
|
54
|
-
describe: "Print info to console",
|
|
55
|
-
type: "boolean",
|
|
56
|
-
demandOption: false,
|
|
57
|
-
default: false,
|
|
58
|
-
}
|
|
14
|
+
const options = {
|
|
15
|
+
"s" : {
|
|
16
|
+
alias: "server",
|
|
17
|
+
describe: `API or Server URL (ex: ${env.BIZA_SERVER_LINK} or http://192.168.1.1 or https://finaapi.imamatek.com)`,
|
|
18
|
+
type: "string",
|
|
19
|
+
demandOption: true,
|
|
20
|
+
default: 'http://localhost'
|
|
59
21
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const minifiedBuffer = Buffer.from(jsMinData)
|
|
127
|
-
printInfo(['Minify : \n', minifiedBuffer.toString()].join(''))
|
|
128
|
-
|
|
129
|
-
let stringifyData = ''
|
|
130
|
-
const isJsonFile = fileName.split('.').pop().toLowerCase().match(/^(json)$/)
|
|
131
|
-
|
|
132
|
-
// compile using node.js VM
|
|
133
|
-
try {
|
|
134
|
-
printInfo('Running script with VM')
|
|
135
|
-
if (isJsonFile) {
|
|
136
|
-
const vmResult = runInNewContext(minifiedBuffer.toString())
|
|
137
|
-
stringifyData = vmResult ? JSON.stringify((typeof vmResult=='function') ? vmResult() : (vmResult || ''), replacer) : ''
|
|
138
|
-
} else {
|
|
139
|
-
// for handling "get" function in local scope (let, var, const)
|
|
140
|
-
const sandbox = vm.createContext({script: ''});
|
|
141
|
-
vm.runInContext(
|
|
142
|
-
minifiedBuffer.toString() + "\n script = (typeof get=='undefined') ? '' : (typeof get=='function') ? get() : (get||'')",
|
|
143
|
-
sandbox
|
|
144
|
-
)
|
|
145
|
-
stringifyData = JSON.stringify(sandbox.script, replacer)
|
|
146
|
-
}
|
|
147
|
-
} catch (err){
|
|
148
|
-
printInfo(`${fileName} : ` + err) // Do not log it as error, we will try to compile the script using ES6 Import
|
|
149
|
-
stringifyData = ''
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const isEmptyScript = (scriptText)=>((typeof scriptText=='undefined') || (scriptText===null) || (scriptText.trim()==='""') || (typeof scriptText=='string' && scriptText.trim().length==0))
|
|
153
|
-
|
|
154
|
-
// compile using ES6 Import
|
|
155
|
-
if (isEmptyScript(stringifyData)) {
|
|
156
|
-
try {
|
|
157
|
-
printInfo('Running script with Import function')
|
|
158
|
-
const lib = await import(`file://${process.cwd()}\\${fileName}`, isJsonFile ? {assert: {type: "json"}} : {})
|
|
159
|
-
stringifyData = JSON.stringify((typeof lib.default=='function') ? lib.default() : (lib.default || ''), replacer).replace(/ /g,'') // remove trailing whitespace
|
|
160
|
-
// stringifyData = stringifyData.replace(/\\r\\n/g,'') // remove line break. We can not do this because "inline css" still requires line breaks
|
|
161
|
-
} catch (error){
|
|
162
|
-
throw(`${fileName} : ` + error)
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (isEmptyScript(stringifyData)) {
|
|
167
|
-
throw(`${fileName} : ` + 'Failed to compile template script.\nPlease make sure the script is correct and not returning empty result')
|
|
168
|
-
} else {
|
|
169
|
-
printInfo('Array function :')
|
|
170
|
-
printInfo(['Stringify : \n', stringifyData].join(''))
|
|
171
|
-
}
|
|
172
|
-
// console.log('RESULT =>', stringifyData)
|
|
173
|
-
printInfo('===================')
|
|
174
|
-
return Buffer.from(stringifyData)
|
|
22
|
+
"i" : {
|
|
23
|
+
alias: "dbIndex",
|
|
24
|
+
default: 2,
|
|
25
|
+
describe: "database index",
|
|
26
|
+
type: "number",
|
|
27
|
+
demandOption: false
|
|
28
|
+
},
|
|
29
|
+
"sub": {
|
|
30
|
+
alias: "subdomain",
|
|
31
|
+
describe: "Subdomain",
|
|
32
|
+
type: "string",
|
|
33
|
+
demandOption: false
|
|
34
|
+
},
|
|
35
|
+
"p": {
|
|
36
|
+
alias: "apiPort",
|
|
37
|
+
default : 212,
|
|
38
|
+
describe: "FINA API Port",
|
|
39
|
+
type: "number",
|
|
40
|
+
demandOption: false,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const addCommandOptions = {
|
|
45
|
+
'd': {
|
|
46
|
+
alias: "workingDir",
|
|
47
|
+
describe: "Path to templates directory",
|
|
48
|
+
type: "string",
|
|
49
|
+
demandOption: false,
|
|
50
|
+
default: process.cwd(),
|
|
51
|
+
},
|
|
52
|
+
'v': {
|
|
53
|
+
alias: "verbose",
|
|
54
|
+
describe: "Print info to console",
|
|
55
|
+
type: "boolean",
|
|
56
|
+
demandOption: false,
|
|
57
|
+
default: false,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const removeCommandOptions = {
|
|
62
|
+
'n': {
|
|
63
|
+
alias: "appName",
|
|
64
|
+
describe: "Application name",
|
|
65
|
+
type: "string",
|
|
66
|
+
demandOption: true,
|
|
67
|
+
default: ""
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
Object
|
|
72
|
+
.keys(options)
|
|
73
|
+
.reduce((app, optKey)=>app = app.option(optKey, options[optKey]), yargs(process.argv.slice(2)))
|
|
74
|
+
.command('add', 'Add Biz-A Application', addCommandOptions, async (options)=>{
|
|
75
|
+
|
|
76
|
+
const prepareKeys = async ()=>{
|
|
77
|
+
const data = Buffer.from(JSON.stringify({issuer: 'CLI', acquirer: 'Client'})).toString('base64')
|
|
78
|
+
const privateKey = fs.readFileSync(`${keyFolderPath}\\cliPrivate.pem`)
|
|
79
|
+
const signature = sign('sha256', data, {key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_PSS_PADDING}).toString('base64')
|
|
80
|
+
const res = await axios.get(env.BIZA_SERVER_LINK+'/api/issuerKey', {params: {data, signature}})
|
|
81
|
+
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'))) {
|
|
82
|
+
const resData = JSON.parse(Buffer.from(res.data.data, 'base64').toString())
|
|
83
|
+
const decryptedAESKey = privateDecrypt({key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_OAEP_PADDING}, Buffer.from(resData.issuer.key, 'base64')).toString()
|
|
84
|
+
const cliSignature = (signedData)=>sign('sha256', signedData, {key: privateKey, passphrase: 'Biz-A@cli', padding: cryptoConstants.RSA_PKCS1_PSS_PADDING}).toString('base64')
|
|
85
|
+
const acquirerData = Buffer.from(JSON.stringify(resData.acquirer)).toString('base64')
|
|
86
|
+
const signature = cliSignature(acquirerData)
|
|
87
|
+
return {encryptKey: decryptedAESKey, metadata: {acquirer: {data: acquirerData, signature}}}
|
|
175
88
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const initializeVector = randomBytes(16) // "iv" is unique for each template file
|
|
179
|
-
const cipher = createCipheriv('aes-256-cbc', Buffer.from(encryptKey, 'base64'), initializeVector)
|
|
180
|
-
return Buffer.concat([initializeVector, cipher.update(data), cipher.final()]) // we put "iv" at beginning of cipherText, seperate it when doing decryption
|
|
89
|
+
else {
|
|
90
|
+
return null
|
|
181
91
|
}
|
|
92
|
+
}
|
|
182
93
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
}})
|
|
197
|
-
if (compressSuccess==false) {
|
|
198
|
-
fs.unlinkSync(fileName)
|
|
199
|
-
retryCount++
|
|
94
|
+
const compressIt = (fileName, folderPath)=>{
|
|
95
|
+
// tar v7.1.0 -> tested 10x times, 2-3x times last compressed file have empty content
|
|
96
|
+
// tar v.7.4.0 -> tested 1000x times, all files have content (used app.test.js)
|
|
97
|
+
let compressSuccess = false
|
|
98
|
+
const maxRetry = 10
|
|
99
|
+
let retryCount = 0
|
|
100
|
+
do {
|
|
101
|
+
tar.c({file: fileName, cwd: folderPath, gzip: {level:9}, strict: true, sync: true}, fs.readdirSync(folderPath))
|
|
102
|
+
compressSuccess = true
|
|
103
|
+
tar.t({file: fileName, cwd: folderPath, sync: true, onentry: (entry)=>{
|
|
104
|
+
if (entry.size==0) {
|
|
105
|
+
compressSuccess = false
|
|
200
106
|
}
|
|
107
|
+
}})
|
|
108
|
+
if (compressSuccess==false) {
|
|
109
|
+
fs.unlinkSync(fileName)
|
|
110
|
+
retryCount++
|
|
201
111
|
}
|
|
202
|
-
while ((compressSuccess==false) && (retryCount<=maxRetry))
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const getFileList = ()=>{
|
|
206
|
-
return (fs.readdirSync(process.cwd())).filter((fileName)=>{
|
|
207
|
-
const stat = fs.statSync(fileName)
|
|
208
|
-
return stat.isFile() && (stat.size>0) && ((fileName.split('.').pop().toLowerCase().match(/^(js)$/) || (fileName.toLowerCase()=='menu.json')))
|
|
209
|
-
})
|
|
210
112
|
}
|
|
113
|
+
while ((compressSuccess==false) && (retryCount<=maxRetry))
|
|
114
|
+
}
|
|
211
115
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
process.chdir(options.workingDir)
|
|
219
|
-
try {
|
|
220
|
-
const bundlingStart = performance.now()
|
|
221
|
-
const rootFolder = './upload/'
|
|
222
|
-
const bundleName = basename(process.cwd()).trim().replace(' ', '').toLowerCase()
|
|
223
|
-
const bundleFolder = rootFolder+bundleName+'/'
|
|
224
|
-
const files = getFileList()
|
|
225
|
-
if (files.length>0) {
|
|
226
|
-
const keys = await prepareKeys()
|
|
227
|
-
let processedFile = 0
|
|
228
|
-
fs.rmSync(rootFolder, {force: true, recursive: true})
|
|
229
|
-
fs.mkdirSync(bundleFolder, {recursive: true})
|
|
230
|
-
for (const file of files) {
|
|
231
|
-
const fileName = file.toLowerCase();
|
|
232
|
-
// if (['finalib.js', 'solib.js'].indexOf(fileName)==-1) {continue}
|
|
233
|
-
const minifiedData = await minifiedIt(fileName);
|
|
234
|
-
const encryptedData = encryptIt(minifiedData, keys.encryptKey)
|
|
235
|
-
if (fileName=='menu.json') {
|
|
236
|
-
keys.metadata['menu'] = encryptedData.toString('base64')
|
|
237
|
-
} else {
|
|
238
|
-
fs.writeFileSync(bundleFolder+fileName, encryptedData.toString('base64'))
|
|
239
|
-
}
|
|
240
|
-
processedFile++
|
|
241
|
-
}
|
|
242
|
-
const bundleFile = `${rootFolder}${bundleName}.tgz`
|
|
243
|
-
compressIt(bundleFile, bundleFolder)
|
|
244
|
-
console.log(`Finished packing ${processedFile} files into "${bundleFile}" (${((performance.now()-bundlingStart)/1000).toFixed(2)}s)`)
|
|
116
|
+
const getFileList = ()=>{
|
|
117
|
+
return (fs.readdirSync(process.cwd())).filter((fileName)=>{
|
|
118
|
+
const stat = fs.statSync(fileName)
|
|
119
|
+
return stat.isFile() && (stat.size>0) && ((fileName.split('.').pop().toLowerCase().match(/^(js)$/) || (fileName.toLowerCase()=='menu.json')))
|
|
120
|
+
})
|
|
121
|
+
}
|
|
245
122
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
123
|
+
async function addApp() {
|
|
124
|
+
/*
|
|
125
|
+
hex => 2 char = 1 bytes => can be encrypted
|
|
126
|
+
base64 => 4 char = 3 bytes => can be encrypted. Smaller size compare to Hex
|
|
127
|
+
utf8 => 1 char = 1 - 4 bytes => can not be encrypted, encryption need precise bytes per Character. Smallest Size compare to Hex and base64
|
|
128
|
+
*/
|
|
129
|
+
process.chdir(options.workingDir)
|
|
130
|
+
try {
|
|
131
|
+
const bundlingStart = performance.now()
|
|
132
|
+
const rootFolder = './upload/'
|
|
133
|
+
const bundleName = basename(process.cwd()).trim().replace(' ', '').toLowerCase()
|
|
134
|
+
const bundleFolder = rootFolder+bundleName+'/'
|
|
135
|
+
const files = getFileList()
|
|
136
|
+
if (files.length>0) {
|
|
137
|
+
const keys = await prepareKeys()
|
|
138
|
+
let processedFile = 0
|
|
139
|
+
fs.rmSync(rootFolder, {force: true, recursive: true})
|
|
140
|
+
fs.mkdirSync(bundleFolder, {recursive: true})
|
|
141
|
+
for (const file of files) {
|
|
142
|
+
const fileName = file.toLowerCase();
|
|
143
|
+
const encryptedScript = encryptScript(await prepareScript(fileName, fs.readFileSync(fileName).toString(), options.verbose), keys.encryptKey)
|
|
144
|
+
if (fileName=='menu.json') {
|
|
145
|
+
keys.metadata['menu'] = encryptedScript.toString('base64')
|
|
259
146
|
} else {
|
|
260
|
-
|
|
147
|
+
fs.writeFileSync(bundleFolder+fileName, encryptedScript.toString('base64'))
|
|
261
148
|
}
|
|
262
|
-
} else {
|
|
263
|
-
console.error('Nothing to upload. Please recheck your app folder.')
|
|
264
|
-
}
|
|
265
|
-
} catch (e) {
|
|
266
|
-
console.error(e.response?.data ? e.response.data : e)
|
|
267
|
-
// console.error({e})
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
149
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
default: ""
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
(options)=>{
|
|
285
|
-
(async () => {
|
|
286
|
-
try {
|
|
287
|
-
const baseUrl = 'fina/rest/TOrmMethod/%22deleteApp%22'
|
|
150
|
+
processedFile++
|
|
151
|
+
}
|
|
152
|
+
const bundleFile = `${rootFolder}${bundleName}.tgz`
|
|
153
|
+
compressIt(bundleFile, bundleFolder)
|
|
154
|
+
console.log(`Finished packing ${processedFile} files into "${bundleFile}" (${((performance.now()-bundlingStart)/1000).toFixed(2)}s)`)
|
|
155
|
+
|
|
156
|
+
// send to API
|
|
157
|
+
const uploadingStart = performance.now()
|
|
158
|
+
const data = (fs.readFileSync(bundleFile)).toString('base64') // *.tgz to base64String
|
|
159
|
+
const baseUrl = 'fina/rest/TOrmMethod/%22setApp%22'
|
|
288
160
|
const url = options.sub ?
|
|
289
161
|
`${options.server}/hub/${baseUrl}?subdomain=${options.sub}` :
|
|
290
162
|
`${options.server}:${options.apiPort}/${baseUrl}`
|
|
291
163
|
const headers = {'Content-Type': 'text/plain'}
|
|
292
|
-
const
|
|
293
|
-
const param = { _parameters: [options.dbIndex, deleteApps] }
|
|
164
|
+
const param = { _parameters: [options.dbIndex, bundleName, data, keys.metadata] }
|
|
294
165
|
const res = await axios.post(url, param, { headers: headers });
|
|
295
|
-
if (res.data
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
} else {
|
|
299
|
-
const failedList = (res.data._f && (typeof res.data._f=='string')) ? res.data._f.trim().replaceAll(' ', '').toLowerCase().split(',') : []
|
|
300
|
-
const removeList = deleteApps.split(',')
|
|
301
|
-
removeList.forEach((app)=>{
|
|
302
|
-
console.log(`${app} ${failedList.indexOf(app)==-1 ? 'removed' : 'not found'}`)
|
|
303
|
-
})
|
|
304
|
-
}
|
|
166
|
+
if (res.data.success) {
|
|
167
|
+
console.log(`Finished uploading "${bundleFile}" (${((performance.now()-uploadingStart)/1000).toFixed(2)}s)`)
|
|
168
|
+
fs.rmSync(rootFolder, {force: true, recursive: true})
|
|
305
169
|
} else {
|
|
306
170
|
console.error(res.data.error)
|
|
307
171
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const errMsg = (e.response?.data ? e.response.data : e)
|
|
311
|
-
console.error(errMsg)
|
|
312
|
-
return errMsg
|
|
172
|
+
} else {
|
|
173
|
+
console.error('Nothing to upload. Please recheck your app folder.')
|
|
313
174
|
}
|
|
314
|
-
}
|
|
175
|
+
} catch (e) {
|
|
176
|
+
console.error(e.response?.data ? e.response.data : e)
|
|
177
|
+
}
|
|
315
178
|
}
|
|
316
|
-
|
|
179
|
+
|
|
180
|
+
await addApp()
|
|
181
|
+
})
|
|
182
|
+
.command('remove', 'Remove Biz-A Application', removeCommandOptions, (options)=>{
|
|
183
|
+
(async () => {
|
|
184
|
+
try {
|
|
185
|
+
const baseUrl = 'fina/rest/TOrmMethod/%22deleteApp%22'
|
|
186
|
+
const url = options.sub ?
|
|
187
|
+
`${options.server}/hub/${baseUrl}?subdomain=${options.sub}` :
|
|
188
|
+
`${options.server}:${options.apiPort}/${baseUrl}`
|
|
189
|
+
const headers = {'Content-Type': 'text/plain'}
|
|
190
|
+
const deleteApps = options.appName.trim().replaceAll(' ', '').toLowerCase()
|
|
191
|
+
const param = { _parameters: [options.dbIndex, deleteApps] }
|
|
192
|
+
const res = await axios.post(url, param, { headers: headers });
|
|
193
|
+
if (res.data?.success) {
|
|
194
|
+
if (deleteApps=='') {
|
|
195
|
+
console.log('All apps removed')
|
|
196
|
+
} else {
|
|
197
|
+
const failedList = (res.data._f && (typeof res.data._f=='string')) ? res.data._f.trim().replaceAll(' ', '').toLowerCase().split(',') : []
|
|
198
|
+
const removeList = deleteApps.split(',')
|
|
199
|
+
removeList.forEach((app)=>{
|
|
200
|
+
console.log(`${app} ${failedList.indexOf(app)==-1 ? 'removed' : 'not found'}`)
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
console.error(res.data.error)
|
|
205
|
+
}
|
|
206
|
+
return res
|
|
207
|
+
} catch (e) {
|
|
208
|
+
const errMsg = (e.response?.data ? e.response.data : e)
|
|
209
|
+
console.error(errMsg)
|
|
210
|
+
return errMsg
|
|
211
|
+
}
|
|
212
|
+
})()
|
|
213
|
+
})
|
|
317
214
|
.recommendCommands()
|
|
318
215
|
.demandCommand(1, 'You need at least one command before moving on')
|
|
319
216
|
.strict()
|
|
320
|
-
.parse();
|
|
217
|
+
.parse();
|
|
218
|
+
|
|
219
|
+
export {options, addCommandOptions, removeCommandOptions}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { prepareScript, encryptScript } from './script.js';
|
|
3
|
+
import { IDLE_SOCKET_TIMEOUT_MILLISECONDS } from './hubEvent.js';
|
|
4
|
+
import { CLIENT_ROOM } from './directHubEvent.js';
|
|
5
|
+
|
|
6
|
+
export async function getAppData(apiOpts){
|
|
7
|
+
return await axios.request({
|
|
8
|
+
timeout : IDLE_SOCKET_TIMEOUT_MILLISECONDS,
|
|
9
|
+
baseURL : `${apiOpts['secure']==true ? 'https://' : 'http://'}${apiOpts['hostname']}:${apiOpts['port']}`,
|
|
10
|
+
url : 'fina/rest/TOrmMethod/%22list%22',
|
|
11
|
+
method : 'POST',
|
|
12
|
+
"data" : {
|
|
13
|
+
"dbIndex": apiOpts['dbindex'],
|
|
14
|
+
"object":{
|
|
15
|
+
"columns":[
|
|
16
|
+
{"title":"app", "data":"SYS$APPS.APPNAME"},
|
|
17
|
+
{"title":"metadata", "data":"SYS$APPS.METADATA"}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
.then(response=>{
|
|
23
|
+
return {error: null, apps: JSON.parse(response.data.data)};
|
|
24
|
+
})
|
|
25
|
+
.catch(err=>{
|
|
26
|
+
return {"error": ((err && (err instanceof Error)) ? err.message : err), apps: []};
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
async function getDeployScript(serverUrl, appList, data){
|
|
31
|
+
let script = await prepareScript(data.fileName, data.script, false);
|
|
32
|
+
if (script) {
|
|
33
|
+
script = await axios.request({
|
|
34
|
+
timeout : IDLE_SOCKET_TIMEOUT_MILLISECONDS,
|
|
35
|
+
baseURL : serverUrl,
|
|
36
|
+
url : 'api/acquirerKey',
|
|
37
|
+
method : 'POST',
|
|
38
|
+
"data" : {"metadata": JSON.stringify(JSON.parse((appList.find((app)=>app['SYS$APPS.APPNAME']===data.appName))['SYS$APPS.METADATA'])['acquirer'])},
|
|
39
|
+
})
|
|
40
|
+
.then(response=>encryptScript(script, response.data).toString('base64'));
|
|
41
|
+
};
|
|
42
|
+
return script;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export async function onScriptChangeFromVsCode(sock, serverUrl, appList, data){
|
|
46
|
+
let script;
|
|
47
|
+
try {
|
|
48
|
+
script = await getDeployScript(serverUrl, appList, data);
|
|
49
|
+
if (script) {
|
|
50
|
+
sock.to(CLIENT_ROOM).emit('scriptDeploy', {...data, ...{script}});
|
|
51
|
+
return {error: null, script};
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
throw new Error('Invalid Script');
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
return {"error": ((err && (err instanceof Error)) ? err.message : err), script};
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const addAppListListener = (sock,argv)=>{
|
|
63
|
+
sock.on('appList', async (data, cb)=>{
|
|
64
|
+
const result = await getAppData(argv);
|
|
65
|
+
cb(result.error, ((result.error) ? [] : result.apps.map((app)=>app['SYS$APPS.APPNAME'])));
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const addOnScriptChangeListener = (sock, argv, deployFn)=>{
|
|
70
|
+
sock.on('scriptChange', async (data, cb)=>{
|
|
71
|
+
const result = await getAppData(argv);
|
|
72
|
+
if (result.error) {
|
|
73
|
+
cb(result);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const deployResult = await deployFn(result.apps, data);
|
|
77
|
+
cb(deployResult.error, deployResult.script);
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const deploymentListenerForVSCode = (socket, argv)=>{
|
|
83
|
+
addAppListListener(socket,argv);
|
|
84
|
+
addOnScriptChangeListener(socket, argv, async (apps, data)=>{
|
|
85
|
+
return (await onScriptChangeFromVsCode(socket, argv['server'], apps, data));
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export async function onScriptChangeFromHubServer(serverUrl, appList, data){
|
|
90
|
+
let script;
|
|
91
|
+
try {
|
|
92
|
+
script = await getDeployScript(serverUrl, appList, data);
|
|
93
|
+
return {error: (script ? null : 'Invalid Script'), script};
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
return {"error": ((err && (err instanceof Error)) ? err.message : err), script};
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const deploymentListenerForHubServer = (socket, argv)=>{
|
|
101
|
+
addAppListListener(socket, argv);
|
|
102
|
+
addOnScriptChangeListener(socket, argv, async (apps, data)=>{
|
|
103
|
+
return (await onScriptChangeFromHubServer(argv['server'], apps, data));
|
|
104
|
+
});
|
|
105
|
+
};
|