biz-a-cli 2.3.70 → 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/package.json CHANGED
@@ -1,73 +1,71 @@
1
1
  {
2
- "name": "biz-a-cli",
3
- "nameDev": "biz-a-cli-dev",
4
- "version": "2.3.70",
5
- "versionDev": "0.0.34",
6
- "description": "",
7
- "main": "bin/index.js",
8
- "type": "module",
9
- "engines": {
10
- "node": ">=20.16.0",
11
- "npm": ">=10.8.1"
12
- },
13
- "scripts": {
14
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch a",
15
- "testOnce": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
16
- "dev": "node --watch server.js",
17
- "hub": "set NODE_ENV=dev&& node --experimental-vm-modules bin/hub.js"
18
- },
19
- "author": "Imamatek",
20
- "license": "ISC",
21
- "bin": {
22
- "uploadbiza": "bin/index.js",
23
- "proxy": "bin/proxy.js",
24
- "hub": "bin/hub.js",
25
- "watcher": "bin/watcher.js",
26
- "uploadapp": "bin/uploadApp.js",
27
- "deleteapp": "bin/deleteApp.js",
28
- "biza": "bin/app.js"
29
- },
30
- "dependencies": {
31
- "axios": "^1.7.8",
32
- "cloudflared": "^0.6.0",
33
- "compression": "^1.7.5",
34
- "cors": "^2.8.5",
35
- "dayjs": "^1.11.10",
36
- "express": "^4.18.3",
37
- "mongodb": "^6.5.0",
38
- "net": "^1.0.2",
39
- "nodemailer": "^6.9.12",
40
- "socket.io": "^4.7.5",
41
- "socket.io-client": "^4.7.5",
42
- "socket.io-stream": "^0.9.1",
43
- "tar": "^7.4.0",
44
- "uglify-js": "^3.19.3",
45
- "web-push": "^3.6.7",
46
- "winston": "^3.13.0",
47
- "yargs": "^17.7.2"
48
- },
49
- "devDependencies": {
50
- "jest": "^29.7.0"
51
- },
52
- "jest": {
53
- "transform": {},
54
- "testMatch": [
55
- "<rootDir>/tests/**",
56
- "!<rootDir>/tests/mockData",
57
- "!<rootDir>/tests/mockData/**"
58
- ],
59
- "testPathIgnorePatterns": [
60
- "<rootDir>/node_modules",
61
- "<rootDir>/tests/mockData"
62
- ],
63
- "watchPathIgnorePatterns": [
64
- "<rootDir>/node_modules",
65
- "<rootDir>/tests/mockData"
66
- ],
67
- "coveragePathIgnorePatterns": [
68
- "<rootDir>/node_modules",
69
- "<rootDir>/tests/mockData",
70
- "<rootDir>/tests/mockData/**"
71
- ]
72
- }
73
- }
2
+ "name": "biz-a-cli",
3
+ "version": "2.3.71",
4
+ "description": "",
5
+ "main": "bin/index.js",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=20.16.0",
9
+ "npm": ">=10.8.1"
10
+ },
11
+ "scripts": {
12
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch a",
13
+ "testOnce": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
14
+ "dev": "node --watch server.js",
15
+ "hub": "set NODE_ENV=dev&& node --experimental-vm-modules bin/hub.js"
16
+ },
17
+ "author": "Imamatek",
18
+ "license": "ISC",
19
+ "bin": {
20
+ "uploadbiza": "bin/index.js",
21
+ "proxy": "bin/proxy.js",
22
+ "hub": "bin/hub.js",
23
+ "watcher": "bin/watcher.js",
24
+ "uploadapp": "bin/uploadApp.js",
25
+ "deleteapp": "bin/deleteApp.js",
26
+ "biza": "bin/app.js"
27
+ },
28
+ "dependencies": {
29
+ "axios": "^1.7.8",
30
+ "cloudflared": "^0.6.0",
31
+ "compression": "^1.7.5",
32
+ "cors": "^2.8.5",
33
+ "dayjs": "^1.11.10",
34
+ "express": "^4.18.3",
35
+ "mongodb": "^6.5.0",
36
+ "net": "^1.0.2",
37
+ "nodemailer": "^6.9.12",
38
+ "socket.io": "^4.7.5",
39
+ "socket.io-client": "^4.7.5",
40
+ "socket.io-stream": "^0.9.1",
41
+ "tar": "^7.4.0",
42
+ "uglify-js": "^3.19.3",
43
+ "web-push": "^3.6.7",
44
+ "winston": "^3.13.0",
45
+ "yargs": "^17.7.2"
46
+ },
47
+ "devDependencies": {
48
+ "jest": "^29.7.0"
49
+ },
50
+ "jest": {
51
+ "transform": {},
52
+ "testMatch": [
53
+ "<rootDir>/tests/**",
54
+ "!<rootDir>/tests/mockData",
55
+ "!<rootDir>/tests/mockData/**"
56
+ ],
57
+ "testPathIgnorePatterns": [
58
+ "<rootDir>/node_modules",
59
+ "<rootDir>/tests/mockData"
60
+ ],
61
+ "watchPathIgnorePatterns": [
62
+ "<rootDir>/node_modules",
63
+ "<rootDir>/tests/mockData"
64
+ ],
65
+ "coveragePathIgnorePatterns": [
66
+ "<rootDir>/node_modules",
67
+ "<rootDir>/tests/mockData",
68
+ "<rootDir>/tests/mockData/**"
69
+ ]
70
+ }
71
+ }
package/tests/app.test.js CHANGED
@@ -96,7 +96,7 @@ describe('Biz-A Apps CLI', () => {
96
96
  })
97
97
 
98
98
  const stressTestCount = 10;
99
- it.each(Array(stressTestCount).fill().map((v, i) => i + 1))(`shall compress and encrypt app scripts (stress test %p of ${stressTestCount})`, async (testNo) => {
99
+ it.each(Array(stressTestCount).fill().map((v, i) => i + 1))(`shall compress and encrypt app scripts (stress test %p of ${stressTestCount})`, async (testNo) => {
100
100
  const mockScripts = {
101
101
  'a.js': {
102
102
  act: 'get = function () {return {modelA: {}}}',
@@ -252,13 +252,130 @@ describe('Biz-A Apps CLI', () => {
252
252
  expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 0][0]).toContain(`Finished packing ${Object.keys(mockScripts).length} files into "./upload/${appName.toLowerCase()}.tgz"`)
253
253
  expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 1][0]).toContain(`Finished uploading "./upload/${appName.toLocaleLowerCase()}.tgz"`)
254
254
 
255
- // fs.rmSync(mockDataFolder + '/' + appName, {recursive: true, force: true})
256
- })
257
-
258
- /* it('Invalid script syntax', async ()=>{
259
- mockValidTemplates({
260
- 'a.js' : {act: 'get = function () {return {modelA: {}}'} // missing close closure at the end
261
- })
255
+ // fs.rmSync(mockDataFolder + '/' + appName, {recursive: true, force: true})
256
+ })
257
+
258
+ it('shall support addApp body scripts when called from other function', async () => {
259
+ const appName = 'bodyScriptSourceApp'
260
+ const workingDir = mockDataFolder + '/' + appName
261
+ const mockAESKey = Buffer.from('hAnHadaJXJaq/9fCFMNmjkrB61CBPXJid6vbtXgG8Ug=', 'base64')
262
+ const expectedScript = '{"fromBody":{"id":1}}'
263
+
264
+ mockValidTemplates(appName, {})
265
+ mockIssuerKeyResponse()
266
+ process.env.BIZA_APP_SKIP_PARSE = '1'
267
+
268
+ try {
269
+ process.argv[1] = process.cwd() + '\\bin'
270
+ const { addApp } = await import('../bin/app.js')
271
+
272
+ const result = await addApp({
273
+ workingDir,
274
+ verbose: false,
275
+ server: 'https://a.b.c',
276
+ apiPort: 1205,
277
+ dbIndex: 2,
278
+ files: [{ name: 'a.js' }],
279
+ body: {
280
+ scripts: {
281
+ 'a.js': 'get = function () { return {fromBody: {id: 1}} }'
282
+ }
283
+ }
284
+ })
285
+
286
+ expect(result.success).toBe(true)
287
+ expect(axios.post).toHaveBeenCalledTimes(1)
288
+
289
+ const apiParams = axios.post.mock.calls[0][1]._parameters
290
+ const templateBuffer = Buffer.from(apiParams[2], 'base64')
291
+ const decryptScripts = {}
292
+ const stream = new Duplex()
293
+ stream.push(templateBuffer)
294
+ stream.push(null)
295
+ stream.pipe(
296
+ tar.t({
297
+ strict: true,
298
+ sync: true,
299
+ onReadEntry: entry => {
300
+ const chunks = []
301
+ entry.on('data', chunk => chunks.push(chunk))
302
+ entry.on('end', () => {
303
+ const cipherText = Buffer.from(Buffer.concat(chunks).toString(), 'base64')
304
+ const decryptInitializeVector = cipherText.subarray(0, 16)
305
+ const decryptData = cipherText.subarray(16)
306
+ const decipher = createDecipheriv('aes-256-cbc', mockAESKey, decryptInitializeVector)
307
+ const decrypted = Buffer.concat([decipher.update(decryptData), decipher.final()])
308
+ decryptScripts[entry.path] = decrypted.toString()
309
+ })
310
+ }
311
+ })
312
+ .on('finish', () => stream.end())
313
+ )
314
+ await finished(stream)
315
+
316
+ expect(decryptScripts['a.js']).toBe(expectedScript)
317
+ } finally {
318
+ delete process.env.BIZA_APP_SKIP_PARSE
319
+ }
320
+ })
321
+
322
+ it('shall resolve app name from SYS$CONFIG APPLICATION_CONFIG and use it for upload', async () => {
323
+ const appName = 'metadataNameSourceApp'
324
+ const workingDir = mockDataFolder + '/' + appName
325
+
326
+ mockValidTemplates(appName, {})
327
+ mockIssuerKeyResponse()
328
+ axios.request = jest.fn().mockResolvedValue({
329
+ data: {
330
+ data: JSON.stringify([
331
+ {
332
+ 'SYS$CONFIG.NAME': 'APPLICATION_CONFIG',
333
+ 'SYS$CONFIG.DATA': JSON.stringify({
334
+ metadata: {
335
+ name: 'BizA Sales App'
336
+ }
337
+ })
338
+ }
339
+ ])
340
+ }
341
+ })
342
+ process.env.BIZA_APP_SKIP_PARSE = '1'
343
+
344
+ try {
345
+ process.argv[1] = process.cwd() + '\\bin'
346
+ const { addApp } = await import('../bin/app.js')
347
+
348
+ const result = await addApp({
349
+ workingDir,
350
+ verbose: false,
351
+ server: 'https://a.b.c',
352
+ apiPort: 1205,
353
+ dbIndex: 2,
354
+ files: [{ name: 'applicationConfig.js' }],
355
+ sub: 'metadata-sub',
356
+ body: {
357
+ scripts: {
358
+ 'applicationConfig.js': 'get = function () { return { metadata: { name: "Name From Body Must Be Ignored" } }; }'
359
+ }
360
+ }
361
+ })
362
+
363
+ expect(result.success).toBe(true)
364
+ expect(axios.post).toHaveBeenCalledTimes(1)
365
+ expect(axios.request).toHaveBeenCalledTimes(1)
366
+
367
+ const apiParams = axios.post.mock.calls[0][1]._parameters
368
+ expect(apiParams[1]).toBe('bizasalesapp')
369
+ expect(apiParams[3]).toHaveProperty('name', 'BizA Sales App')
370
+ } finally {
371
+ delete process.env.BIZA_APP_SKIP_PARSE
372
+ }
373
+ })
374
+
375
+ /* it('Invalid script syntax', async ()=>{
376
+ mockValidTemplates({
377
+ 'a.js' : {act: 'get = function () {return {modelA: {}}'} // missing close closure at the end
378
+ })
262
379
  mockIssuerKeyResponse()
263
380
  await runCommand('add', '-s', 'https://a.b.c', '-p', '1205', '-i', '2', '-d', appFolderName, "-v")
264
381
 
@@ -0,0 +1,220 @@
1
+ import { Server as ioServer } from 'socket.io';
2
+ import { createServer } from 'node:http';
3
+ import { io as ioClient } from 'socket.io-client';
4
+ import { jest } from '@jest/globals';
5
+
6
+ const mockAddApp = jest.fn().mockResolvedValue({ success: true, data: { uploaded: true } });
7
+ jest.unstable_mockModule('../bin/app.js', () => ({
8
+ addApp: mockAddApp
9
+ }));
10
+
11
+ const { streamEvent } = await import('../bin/hubEvent.js');
12
+
13
+ let socketsBySubdomain = {};
14
+
15
+ const createSockTunnel2CLI = (socket) => {
16
+ return (subdomain, responseCb) => {
17
+ const responseCallback = (error) => { if (responseCb) responseCb(error) }
18
+ let subdomainStr = subdomain.toString();
19
+
20
+ socketsBySubdomain[subdomainStr] = socket;
21
+ socket.subdomain = subdomainStr;
22
+
23
+ responseCallback(null);
24
+ }
25
+ }
26
+
27
+ const deleteSubdomain = (socket) => () => {
28
+ if (socket.subdomain) {
29
+ delete socketsBySubdomain[socket.subdomain];
30
+ }
31
+ }
32
+
33
+ const toPromise = (cb) => new Promise((resolve, reject) => {
34
+ cb(resolve)
35
+ setTimeout(() => reject(new Error('timeout')), 1000)
36
+ })
37
+
38
+ describe('Hub publish request tests', () => {
39
+ let bizAServerSocket, httpServer, port;
40
+
41
+ beforeAll((done) => {
42
+ httpServer = createServer();
43
+ bizAServerSocket = new ioServer(httpServer);
44
+ bizAServerSocket.on('connection', (socket) => {
45
+ socket.on('createTunnel', createSockTunnel2CLI(socket));
46
+ socket.on('disconnect', deleteSubdomain(socket));
47
+ });
48
+ httpServer.listen(() => {
49
+ port = httpServer.address().port;
50
+ done();
51
+ });
52
+ });
53
+
54
+ beforeEach(() => {
55
+ mockAddApp.mockClear();
56
+ delete process.env.BIZA_APP_SKIP_PARSE;
57
+ });
58
+
59
+ afterAll(() => {
60
+ bizAServerSocket.close();
61
+ httpServer.close();
62
+ });
63
+
64
+ test('publish-req should execute addApp and pass body scripts', async () => {
65
+ const subdomain = 'publish-room';
66
+ const socket = ioClient(`http://localhost:${port}`);
67
+ await streamEvent(socket, {
68
+ server: `http://localhost:${port}`,
69
+ subdomain,
70
+ hostname: 'localhost',
71
+ port: 212,
72
+ serverport: 3002,
73
+ dbindex: 7
74
+ });
75
+
76
+ const publishBody = {
77
+ options: {
78
+ workingDir: 'c:/tmp/app',
79
+ verbose: true,
80
+ server: 'https://srv',
81
+ apiPort: '1205',
82
+ dbIndex: '2',
83
+ sub: 'unit-sub',
84
+ files: [{ name: 'a.js' }]
85
+ },
86
+ scripts: {
87
+ 'a.js': 'get = function () { return {unit: true} }'
88
+ }
89
+ };
90
+
91
+ const response = await toPromise(resolve => socketsBySubdomain[subdomain].emit(
92
+ 'publish-req',
93
+ {
94
+ path: '/publish',
95
+ method: 'POST',
96
+ query: { subdomain },
97
+ body: publishBody,
98
+ headers: { 'content-type': 'application/json' }
99
+ },
100
+ (cb) => resolve(cb)
101
+ ));
102
+
103
+ expect(mockAddApp).toHaveBeenCalledTimes(1);
104
+ expect(mockAddApp).toHaveBeenCalledWith({
105
+ workingDir: 'c:/tmp/app',
106
+ verbose: true,
107
+ server: 'https://srv',
108
+ apiPort: 1205,
109
+ dbIndex: 2,
110
+ sub: 'unit-sub',
111
+ files: [{ name: 'a.js' }],
112
+ body: publishBody
113
+ });
114
+ expect(response).toStrictEqual({ success: true, data: { uploaded: true } });
115
+ expect(process.env.BIZA_APP_SKIP_PARSE).toBeUndefined();
116
+
117
+ socket.disconnect();
118
+ });
119
+
120
+ test('publish-req should treat data.body as direct addApp http payload', async () => {
121
+ const subdomain = 'publish-room-http';
122
+ const socket = ioClient(`http://localhost:${port}`);
123
+ await streamEvent(socket, {
124
+ server: `http://localhost:${port}`,
125
+ subdomain,
126
+ hostname: 'localhost',
127
+ port: 212,
128
+ serverport: 3002,
129
+ dbindex: 7
130
+ });
131
+
132
+ const publishBody = {
133
+ verbose: false,
134
+ fileList: [{ name: 'a.js' }],
135
+ scripts: {
136
+ 'a.js': 'get = function () { return {direct: true} }'
137
+ }
138
+ };
139
+
140
+ const response = await toPromise(resolve => socketsBySubdomain[subdomain].emit(
141
+ 'publish-req',
142
+ {
143
+ path: '/publish',
144
+ method: 'POST',
145
+ query: { subdomain },
146
+ body: publishBody,
147
+ headers: { 'content-type': 'application/json' }
148
+ },
149
+ (cb) => resolve(cb)
150
+ ));
151
+
152
+ expect(mockAddApp).toHaveBeenCalledTimes(1);
153
+ expect(mockAddApp).toHaveBeenCalledWith({
154
+ workingDir: expect.any(String),
155
+ verbose: false,
156
+ server: `http://localhost:${port}`,
157
+ apiPort: 212,
158
+ dbIndex: 7,
159
+ sub: subdomain,
160
+ files: [{ name: 'a.js' }],
161
+ body: publishBody
162
+ });
163
+ expect(response).toStrictEqual({ success: true, data: { uploaded: true } });
164
+ expect(process.env.BIZA_APP_SKIP_PARSE).toBeUndefined();
165
+
166
+ socket.disconnect();
167
+ });
168
+
169
+ test('publish-req should support double-wrapper payload from HTTP client', async () => {
170
+ const subdomain = 'publish-room-double-wrapper';
171
+ const socket = ioClient(`http://localhost:${port}`);
172
+ await streamEvent(socket, {
173
+ server: `http://localhost:${port}`,
174
+ subdomain,
175
+ hostname: 'localhost',
176
+ port: 212,
177
+ serverport: 3002,
178
+ dbindex: 7
179
+ });
180
+
181
+ const publishBody = {
182
+ fileList: [{ name: 'menu.json' }, { name: 'loginform.js' }],
183
+ scripts: {
184
+ 'menu.json': '[{"menuName":"","caption":"Home","link":["./main"],"subMenu":[]}]',
185
+ 'loginform.js': 'get = function () { return {model:{}, tableName:"", fields:[], functions:{ beforeLoadData: function (id) {} }}; };'
186
+ }
187
+ };
188
+
189
+ const response = await toPromise(resolve => socketsBySubdomain[subdomain].emit(
190
+ 'publish-req',
191
+ {
192
+ path: '/publish',
193
+ method: 'POST',
194
+ query: { subdomain },
195
+ headers: { 'content-type': 'application/json' },
196
+ body: {
197
+ method: 'POST',
198
+ query: { subdomain },
199
+ body: publishBody
200
+ }
201
+ },
202
+ (cb) => resolve(cb)
203
+ ));
204
+
205
+ expect(mockAddApp).toHaveBeenCalledTimes(1);
206
+ expect(mockAddApp).toHaveBeenCalledWith({
207
+ workingDir: expect.any(String),
208
+ verbose: false,
209
+ server: `http://localhost:${port}`,
210
+ apiPort: 212,
211
+ dbIndex: 7,
212
+ sub: subdomain,
213
+ files: [{ name: 'menu.json' }, { name: 'loginform.js' }],
214
+ body: publishBody
215
+ });
216
+ expect(response).toStrictEqual({ success: true, data: { uploaded: true } });
217
+
218
+ socket.disconnect();
219
+ });
220
+ });