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 +388 -148
- package/bin/directHubEvent.js +2 -2
- package/bin/hub.js +6 -6
- package/bin/hubEvent.js +171 -44
- package/bin/log/debug.log +7 -0
- package/bin/log/error.log +7 -0
- package/bin/log/exception.log +6 -0
- package/bin/log/info.log +7 -0
- package/envs/env.dev.js +2 -2
- package/envs/env.js +1 -0
- package/package.json +70 -72
- package/tests/app.test.js +125 -8
- package/tests/hub.test.js +647 -484
- package/tests/hubPublish.test.js +220 -0
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
|
|
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
|
-
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
239
|
+
const resolveAppDisplayNameFromApplicationConfig = async ({ server, apiPort, dbIndex, sub }) => {
|
|
240
|
+
if ((typeof axios.request) !== 'function') {
|
|
241
|
+
return ''
|
|
242
|
+
}
|
|
129
243
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
263
|
+
})
|
|
140
264
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
359
|
+
fs.writeFileSync(bundleFolder + fileName, encryptedScript.toString('base64'))
|
|
221
360
|
}
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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, (
|
|
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 =
|
|
234
|
-
`${
|
|
235
|
-
`${
|
|
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 =
|
|
238
|
-
const param = { _parameters: [
|
|
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 }
|
package/bin/directHubEvent.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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 '
|
|
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
|
|
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} `);
|