biz-a-cli 2.3.71 → 2.3.72

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
@@ -1,508 +1,671 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import yargs from "yargs"
4
- import axios from "axios"
5
- import fs from "fs"
6
- import * as tar from "tar"
7
- import { verify, sign, privateDecrypt, constants as cryptoConstants } from "node:crypto"
8
- import path, { basename } from "node:path"
9
- import { env } from "../envs/env.js"
10
- import { prepareScript, encryptScript } from "./script.js"
11
- import { spawn } from "node:child_process"
3
+ import yargs from "yargs";
4
+ import axios from "axios";
5
+ import fs from "fs";
6
+ import * as tar from "tar";
7
+ import {
8
+ verify,
9
+ sign,
10
+ privateDecrypt,
11
+ constants as cryptoConstants,
12
+ } from "node:crypto";
13
+ import path, { basename } from "node:path";
14
+ import { env } from "../envs/env.js";
15
+ import { prepareScript, encryptScript } from "./script.js";
16
+ import { spawn } from "node:child_process";
12
17
 
13
18
  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()
19
+ const scriptPath =
20
+ typeof process.argv[1] === "string" && process.argv[1].length > 0
21
+ ? process.argv[1]
22
+ : path.join(import.meta.dirname, "app.js");
23
+ const normalizedPath = scriptPath.replaceAll("/", "\\");
24
+ const binPathPos = normalizedPath.lastIndexOf("\\bin");
25
+ if (binPathPos >= 0) {
26
+ return normalizedPath.substring(0, binPathPos) + "\\key";
27
+ }
28
+ return path.resolve(import.meta.dirname, "..", "key");
29
+ };
30
+
31
+ const keyFolderPath = getKeyFolderPath();
26
32
 
27
33
  const options = {
28
- "s": {
29
- alias: "server",
30
- describe: `API or Server URL (ex: ${env.BIZA_SERVER_LINK} or http://192.168.1.1 or https://finaapi.imamatek.com)`,
31
- type: "string",
32
- demandOption: true,
33
- default: 'http://localhost'
34
- },
35
- "i": {
36
- alias: "dbIndex",
37
- default: 2,
38
- describe: "database index",
39
- type: "number",
40
- demandOption: false
41
- },
42
- "sub": {
43
- alias: "subdomain",
44
- describe: "Subdomain",
45
- type: "string",
46
- demandOption: false
47
- },
48
- "p": {
49
- alias: "apiPort",
50
- default: 212,
51
- describe: "FINA API Port",
52
- type: "number",
53
- demandOption: false,
54
- }
55
- }
34
+ s: {
35
+ alias: "server",
36
+ describe: `API or Server URL (ex: ${env.BIZA_SERVER_LINK} or http://192.168.1.1 or https://finaapi.imamatek.com)`,
37
+ type: "string",
38
+ demandOption: true,
39
+ default: "http://localhost",
40
+ },
41
+ i: {
42
+ alias: "dbIndex",
43
+ default: 2,
44
+ describe: "database index",
45
+ type: "number",
46
+ demandOption: false,
47
+ },
48
+ sub: {
49
+ alias: "subdomain",
50
+ describe: "Subdomain",
51
+ type: "string",
52
+ demandOption: false,
53
+ },
54
+ p: {
55
+ alias: "apiPort",
56
+ default: 212,
57
+ describe: "FINA API Port",
58
+ type: "number",
59
+ demandOption: false,
60
+ },
61
+ };
56
62
 
57
63
  const addCommandOptions = {
58
- 'd': {
59
- alias: "workingDir",
60
- describe: "Path to templates directory",
61
- type: "string",
62
- demandOption: false,
63
- default: process.cwd(),
64
- },
65
- 'v': {
66
- alias: "verbose",
67
- describe: "Print info to console",
68
- type: "boolean",
69
- demandOption: false,
70
- default: false,
71
- }
72
- }
64
+ d: {
65
+ alias: "workingDir",
66
+ describe: "Path to templates directory",
67
+ type: "string",
68
+ demandOption: false,
69
+ default: process.cwd(),
70
+ },
71
+ v: {
72
+ alias: "verbose",
73
+ describe: "Print info to console",
74
+ type: "boolean",
75
+ demandOption: false,
76
+ default: false,
77
+ },
78
+ };
73
79
 
74
80
  const removeCommandOptions = {
75
- 'n': {
76
- alias: "appName",
77
- describe: "Application name",
78
- type: "string",
79
- demandOption: true,
80
- default: ""
81
- }
82
- }
81
+ n: {
82
+ alias: "appName",
83
+ describe: "Application name",
84
+ type: "string",
85
+ demandOption: true,
86
+ default: "",
87
+ },
88
+ };
83
89
 
84
90
  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
- }
91
+ const data = Buffer.from(
92
+ JSON.stringify({ issuer: "CLI", acquirer: "Client" }),
93
+ ).toString("base64");
94
+ const privateKey = fs.readFileSync(`${keyFolderPath}\\cliPrivate.pem`);
95
+ const signature = sign("sha256", data, {
96
+ key: privateKey,
97
+ passphrase: "Biz-A@cli",
98
+ padding: cryptoConstants.RSA_PKCS1_PSS_PADDING,
99
+ }).toString("base64");
100
+ const res = await axios.get(env.BIZA_SERVER_LINK + "/api/issuerKey", {
101
+ params: { data, signature },
102
+ });
103
+ if (
104
+ res.data.data != null &&
105
+ verify(
106
+ "sha256",
107
+ res.data.data,
108
+ {
109
+ key: fs.readFileSync(`${keyFolderPath}\\serverPublic.pem`),
110
+ padding: cryptoConstants.RSA_PKCS1_PSS_PADDING,
111
+ },
112
+ Buffer.from(res.data.signature, "base64"),
113
+ )
114
+ ) {
115
+ const resData = JSON.parse(
116
+ Buffer.from(res.data.data, "base64").toString(),
117
+ );
118
+ const decryptedAESKey = privateDecrypt(
119
+ {
120
+ key: privateKey,
121
+ passphrase: "Biz-A@cli",
122
+ padding: cryptoConstants.RSA_PKCS1_OAEP_PADDING,
123
+ },
124
+ Buffer.from(resData.issuer.key, "base64"),
125
+ ).toString();
126
+ const cliSignature = (signedData) =>
127
+ sign("sha256", signedData, {
128
+ key: privateKey,
129
+ passphrase: "Biz-A@cli",
130
+ padding: cryptoConstants.RSA_PKCS1_PSS_PADDING,
131
+ }).toString("base64");
132
+ const acquirerData = Buffer.from(
133
+ JSON.stringify(resData.acquirer),
134
+ ).toString("base64");
135
+ const signature = cliSignature(acquirerData);
136
+ return {
137
+ encryptKey: decryptedAESKey,
138
+ metadata: { acquirer: { data: acquirerData, signature } },
139
+ };
140
+ } else {
141
+ return null;
142
+ }
143
+ };
101
144
 
102
145
  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
- }
116
- }
117
- })
118
- if (compressSuccess == false) {
119
- fs.unlinkSync(fileName)
120
- retryCount++
121
- }
122
- }
123
- while ((compressSuccess == false) && (retryCount <= maxRetry))
124
- }
146
+ // tar v7.1.0 -> tested 10x times, 2-3x times last compressed file have empty content
147
+ // tar v.7.4.0 -> tested 1000x times, all files have content (used app.test.js)
148
+ let compressSuccess = false;
149
+ const maxRetry = 10;
150
+ let retryCount = 0;
151
+ do {
152
+ tar.c(
153
+ {
154
+ file: fileName,
155
+ cwd: folderPath,
156
+ gzip: { level: 9 },
157
+ strict: true,
158
+ sync: true,
159
+ },
160
+ fs.readdirSync(folderPath),
161
+ );
162
+ compressSuccess = true;
163
+ tar.t({
164
+ file: fileName,
165
+ cwd: folderPath,
166
+ sync: true,
167
+ onentry: (entry) => {
168
+ if (entry.size == 0) {
169
+ compressSuccess = false;
170
+ }
171
+ },
172
+ });
173
+ if (compressSuccess == false) {
174
+ fs.unlinkSync(fileName);
175
+ retryCount++;
176
+ }
177
+ } while (compressSuccess == false && retryCount <= maxRetry);
178
+ };
125
179
 
126
180
  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
- }
181
+ if (typeof file === "string") {
182
+ return file.trim();
183
+ }
184
+ if (file && typeof file === "object") {
185
+ const fileName = file.fileName || file.name || file.path;
186
+ return (fileName || "").toString().trim();
187
+ }
188
+ return "";
189
+ };
136
190
 
137
191
  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()
192
+ if (Array.isArray(files)) {
193
+ return files
194
+ .map((file) => getNormalizedFileName(file))
195
+ .filter((fileName) => fileName.length > 0);
196
+ }
197
+ if (typeof files === "string") {
198
+ return files
199
+ .split(",")
200
+ .map((f) => f.trim())
201
+ .filter((f) => f.length > 0);
202
+ }
203
+ return null;
204
+ };
205
+
206
+ const normalizeAppName = (value) =>
207
+ String(value ?? "")
208
+ .trim()
209
+ .replace(/\s+/g, "")
210
+ .replace(/-/g, "")
211
+ .toLowerCase();
153
212
 
154
213
  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
179
- }
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
- }
214
+ const scriptMap = new Map();
215
+ if (!body || typeof body !== "object") {
216
+ return scriptMap;
217
+ }
218
+
219
+ const setScript = (name, content) => {
220
+ const fileName = (name || "").toString().trim().toLowerCase();
221
+ if (fileName.length === 0) {
222
+ return;
223
+ }
224
+ if (typeof content === "string") {
225
+ scriptMap.set(fileName, content);
226
+ } else if (Buffer.isBuffer(content)) {
227
+ scriptMap.set(fileName, content.toString());
228
+ }
229
+ };
230
+
231
+ const fromObjects = (list) => {
232
+ if (!Array.isArray(list)) {
233
+ return;
234
+ }
235
+ list.forEach((item) => {
236
+ if (!item || typeof item !== "object") {
237
+ return;
238
+ }
239
+ const fileName = item.fileName || item.name || item.path;
240
+ const content = item.content ?? item.data ?? item.script;
241
+ setScript(fileName, content);
242
+ });
243
+ };
244
+
245
+ fromObjects(body.files);
246
+ fromObjects(body.fileList);
247
+
248
+ if (body.fileContents && typeof body.fileContents === "object") {
249
+ Object.entries(body.fileContents).forEach(([name, content]) =>
250
+ setScript(name, content),
251
+ );
252
+ }
253
+ if (body.scripts && typeof body.scripts === "object") {
254
+ Object.entries(body.scripts).forEach(([name, content]) =>
255
+ setScript(name, content),
256
+ );
257
+ }
258
+
259
+ return scriptMap;
260
+ };
198
261
 
199
262
  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
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
- }
263
+ const fileList = normalizeFileList(files);
264
+ const candidateFiles = fileList || fs.readdirSync(workingDir);
265
+
266
+ return candidateFiles.filter((fileName) => {
267
+ const normalizedName = fileName.toString().trim();
268
+ const filePath = path.isAbsolute(normalizedName)
269
+ ? normalizedName
270
+ : path.join(workingDir, normalizedName);
271
+ if (!fs.existsSync(filePath)) {
272
+ return false;
273
+ }
274
+ const stat = fs.statSync(filePath);
275
+ const baseName = path.basename(normalizedName);
276
+ return (
277
+ stat.isFile() &&
278
+ stat.size > 0 &&
279
+ (baseName
280
+ .split(".")
281
+ .pop()
282
+ .toLowerCase()
283
+ .match(/^(js)$/) ||
284
+ baseName.toLowerCase() == "menu.json")
285
+ );
286
+ });
287
+ };
214
288
 
215
289
  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
235
- }
236
- }
237
- }
238
-
239
- const resolveAppDisplayNameFromApplicationConfig = async ({ server, apiPort, dbIndex, sub }) => {
240
- if ((typeof axios.request) !== 'function') {
241
- return ''
242
- }
243
-
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
- ]
261
- }
262
- }
263
- })
264
-
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 []
273
- }
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 ''
282
- }
283
-
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
- }
290
+ if (rawConfig == null) {
291
+ return null;
292
+ }
293
+ if (typeof rawConfig === "object") {
294
+ return rawConfig;
295
+ }
296
+
297
+ const rawText = String(rawConfig).trim();
298
+ if (!rawText) {
299
+ return null;
300
+ }
301
+
302
+ try {
303
+ return JSON.parse(rawText);
304
+ } catch {
305
+ try {
306
+ return Function(
307
+ `${rawText}; return (typeof get === 'function') ? get() : null;`,
308
+ )();
309
+ } catch {
310
+ return null;
311
+ }
312
+ }
313
+ };
314
+
315
+ const resolveAppDisplayNameFromApplicationConfig = async ({
316
+ server,
317
+ apiPort,
318
+ dbIndex,
319
+ sub,
320
+ }) => {
321
+ if (typeof axios.request !== "function") {
322
+ return "";
323
+ }
324
+
325
+ const baseUrl = "fina/rest/TOrmMethod/%22list%22";
326
+ const url = sub
327
+ ? `${server}/hub/${baseUrl}?subdomain=${sub}`
328
+ : `${server}:${apiPort}/${baseUrl}`;
329
+
330
+ try {
331
+ const response = await axios.request({
332
+ method: "POST",
333
+ url,
334
+ headers: { "Content-Type": "text/plain" },
335
+ data: {
336
+ dbIndex,
337
+ object: {
338
+ columns: [
339
+ { title: "name", data: "SYS$CONFIG.NAME" },
340
+ { title: "data", data: "SYS$CONFIG.DATA" },
341
+ ],
342
+ },
343
+ },
344
+ });
345
+
346
+ const rawRows = response?.data?.data;
347
+ const rows = Array.isArray(rawRows)
348
+ ? rawRows
349
+ : (() => {
350
+ try {
351
+ return JSON.parse(rawRows || "[]");
352
+ } catch {
353
+ return [];
354
+ }
355
+ })();
356
+
357
+ const applicationConfigRow = rows.find((row) => {
358
+ const name = String(
359
+ row?.["SYS$CONFIG.NAME"] ?? row?.NAME ?? row?.name ?? "",
360
+ )
361
+ .trim()
362
+ .toUpperCase();
363
+ return name === "APPLICATION_CONFIG";
364
+ });
365
+ if (!applicationConfigRow) {
366
+ return "";
367
+ }
368
+
369
+ const applicationConfig = parseApplicationConfigData(
370
+ applicationConfigRow?.["SYS$CONFIG.DATA"] ??
371
+ applicationConfigRow?.DATA ??
372
+ applicationConfigRow?.data,
373
+ );
374
+ const displayName = applicationConfig?.metadata?.name;
375
+ return typeof displayName === "string" ? displayName.trim() : "";
376
+ } catch {
377
+ return "";
378
+ }
379
+ };
293
380
 
294
381
  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
382
+ workingDir = process.cwd(),
383
+ verbose = false,
384
+ server = "http://localhost",
385
+ apiPort = 212,
386
+ dbIndex = 2,
387
+ sub,
388
+ files = null,
389
+ body = null,
303
390
  } = {}) {
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')
358
- } else {
359
- fs.writeFileSync(bundleFolder + fileName, encryptedScript.toString('base64'))
360
- }
361
-
362
- processedFile++
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
- }
391
+ const oldCwd = process.cwd();
392
+
393
+ /*
394
+ hex => 2 char = 1 bytes => can be encrypted
395
+ base64 => 4 char = 3 bytes => can be encrypted. Smaller size compare to Hex
396
+ utf8 => 1 char = 1 - 4 bytes => can not be encrypted, encryption need precise bytes per Character. Smallest Size compare to Hex and base64
397
+ */
398
+ try {
399
+ process.chdir(path.resolve(workingDir));
400
+
401
+ const bundlingStart = performance.now();
402
+ const rootFolder = "./upload/";
403
+ const bundleName = normalizeAppName(basename(process.cwd()));
404
+ const bundleFolder = rootFolder + bundleName + "/";
405
+ const sourceFiles = getFileList({ workingDir: process.cwd(), files });
406
+ const bodyScripts = normalizeBodyScripts(body);
407
+ const requestedFiles = normalizeFileList(files);
408
+ const bodySourceFiles = Array.from(bodyScripts.keys()).filter(
409
+ (fileName) => {
410
+ const isSupportedFile =
411
+ fileName.match(/\.js$/) || fileName === "menu.json";
412
+ if (!isSupportedFile) {
413
+ return false;
414
+ }
415
+ if (!requestedFiles) {
416
+ return true;
417
+ }
418
+ return requestedFiles.some(
419
+ (requested) =>
420
+ path.basename(requested).toLowerCase() === fileName,
421
+ );
422
+ },
423
+ );
424
+ const mergedSourceFiles = Array.from(
425
+ new Set([...sourceFiles, ...bodySourceFiles]),
426
+ );
427
+
428
+ if (mergedSourceFiles.length > 0) {
429
+ const keys = await prepareKeys();
430
+ if (!keys) {
431
+ const msg = "Can not prepare encryption keys";
432
+ console.error(msg);
433
+ return { success: false, error: msg };
434
+ }
435
+
436
+ let processedFile = 0;
437
+ fs.rmSync(rootFolder, { force: true, recursive: true });
438
+ fs.mkdirSync(bundleFolder, { recursive: true });
439
+
440
+ for (const sourceFile of mergedSourceFiles) {
441
+ const sourceFilePath = path.isAbsolute(sourceFile)
442
+ ? sourceFile
443
+ : path.resolve(process.cwd(), sourceFile);
444
+ const fileName = path.basename(sourceFile).toLowerCase();
445
+ const scriptSource = bodyScripts.has(fileName)
446
+ ? bodyScripts.get(fileName)
447
+ : fs.readFileSync(sourceFilePath).toString();
448
+ const preparedScript = await prepareScript(
449
+ fileName,
450
+ scriptSource,
451
+ verbose,
452
+ );
453
+ const encryptedScript = encryptScript(
454
+ preparedScript,
455
+ keys.encryptKey,
456
+ );
457
+ if (fileName == "menu.json") {
458
+ keys.metadata["menu"] = encryptedScript.toString("base64");
459
+ } else {
460
+ fs.writeFileSync(
461
+ bundleFolder + fileName,
462
+ encryptedScript.toString("base64"),
463
+ );
464
+ }
465
+
466
+ processedFile++;
467
+ }
468
+ const bundleFile = `${rootFolder}${bundleName}.tgz`;
469
+ compressIt(bundleFile, bundleFolder);
470
+ console.log(
471
+ `Finished packing ${processedFile} files into "${bundleFile}" (${((performance.now() - bundlingStart) / 1000).toFixed(2)}s)`,
472
+ );
473
+
474
+ // send to API
475
+ const uploadingStart = performance.now();
476
+ const data = fs.readFileSync(bundleFile).toString("base64"); // *.tgz to base64String
477
+ const baseUrl = "fina/rest/TOrmMethod/%22setApp%22";
478
+
479
+ const url = sub
480
+ ? `${server}/hub/${baseUrl}?subdomain=${sub}`
481
+ : `${server}:${apiPort}/${baseUrl}`;
482
+
483
+ const headers = { "Content-Type": "text/plain" };
484
+ const appDisplayName =
485
+ await resolveAppDisplayNameFromApplicationConfig({
486
+ server,
487
+ apiPort,
488
+ dbIndex,
489
+ sub,
490
+ });
491
+ const useApplicationConfigName = body != null;
492
+ if (useApplicationConfigName && appDisplayName.length > 0) {
493
+ keys.metadata["name"] = appDisplayName;
494
+ }
495
+ const uploadAppName = normalizeAppName(
496
+ (useApplicationConfigName ? appDisplayName : "") || bundleName,
497
+ );
498
+ const param = {
499
+ _parameters: [dbIndex, uploadAppName, data, keys.metadata],
500
+ };
501
+
502
+ const res = await axios.post(url, param, { headers: headers });
503
+ if (res.data.success) {
504
+ console.log(
505
+ `Finished uploading "${bundleFile}" (${((performance.now() - uploadingStart) / 1000).toFixed(2)}s)`,
506
+ );
507
+ fs.rmSync(rootFolder, { force: true, recursive: true });
508
+ return { success: true, data: res.data };
509
+ }
510
+
511
+ console.error(res.data.error);
512
+ return { success: false, error: res.data.error, data: res.data };
513
+ }
514
+
515
+ const msg = "Nothing to upload. Please recheck your app folder.";
516
+ console.error(msg);
517
+ return { success: false, error: msg };
518
+ } catch (e) {
519
+ const errMsg = e.response?.data ? e.response.data : e;
520
+ console.error(errMsg);
521
+ return { success: false, error: errMsg };
522
+ } finally {
523
+ process.chdir(oldCwd);
524
+ }
403
525
  }
404
526
 
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() || ''
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
- })
527
+ async function runUnitTests(options) {
528
+ //SCY BZ 4331
529
+ return new Promise((resolve) => {
530
+ let jestCommand = ["--no-install", "jest", "--json"];
531
+ let output = "";
532
+
533
+ const testDir = path.join(options.workingDir, "test");
534
+ const workDir = path.resolve(options.workingDir);
535
+ try {
536
+ process.chdir(testDir);
537
+ } catch (error) {
538
+ if (error.code === "ENOENT") {
539
+ jestCommand.push("--passWithNoTests");
540
+ }
541
+ }
542
+ process.chdir(workDir);
543
+
544
+ const child =
545
+ process.platform === "win32"
546
+ ? spawn("cmd.exe", ["/d", "/s", "/c", "npx", ...jestCommand])
547
+ : spawn("npx", jestCommand);
548
+ const collectOutput = (data) => {
549
+ output += data?.toString() || "";
550
+ };
551
+ child.stderr?.on("data", collectOutput);
552
+ child.stdout?.on("data", () => {}); // SCY BZ 4363, ref : https://nodejs.org/download/release/v22.19.0/docs/api/child_process.html
553
+
554
+ child.on("error", (e) => {
555
+ output += e?.message ? `\n${e.message}` : "";
556
+ });
557
+
558
+ child.on("close", async (code) => {
559
+ console.log("====================");
560
+ console.log(output);
561
+ console.log("====================");
562
+ const noTestsFound = output.includes("No tests found");
563
+ const missingJest =
564
+ output.includes("missing packages") && output.includes("jest");
565
+ const passWithNoTests = jestCommand.includes("--passWithNoTests");
566
+
567
+ if (
568
+ code == 0 ||
569
+ (code == 1 && noTestsFound) ||
570
+ (code == 1 && missingJest && passWithNoTests)
571
+ ) {
572
+ await addApp({
573
+ workingDir: workDir,
574
+ verbose: options.verbose,
575
+ server: options.server,
576
+ apiPort: options.apiPort,
577
+ dbIndex: options.dbIndex,
578
+ sub: options.sub,
579
+ files: options.files,
580
+ });
581
+ } else {
582
+ console.error("Biz-A Add aborted");
583
+ }
584
+ resolve();
585
+ });
586
+ });
460
587
  }
461
588
 
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)
467
- })
468
- .command('remove', 'Remove Biz-A Application', removeCommandOptions, (commandOptions) => {
469
- ; (async () => {
470
- try {
471
- const baseUrl = 'fina/rest/TOrmMethod/%22deleteApp%22'
472
- const url = commandOptions.sub ?
473
- `${commandOptions.server}/hub/${baseUrl}?subdomain=${commandOptions.sub}` :
474
- `${commandOptions.server}:${commandOptions.apiPort}/${baseUrl}`
475
- const headers = { 'Content-Type': 'text/plain' }
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 })
479
- if (res.data?.success) {
480
- if (deleteApps == '') {
481
- console.log('All apps removed')
482
- } else {
483
- const failedList = (res.data._f && (typeof res.data._f == 'string')) ? res.data._f.trim().replaceAll(' ', '').toLowerCase().split(',') : []
484
- const removeList = deleteApps.split(',')
485
- removeList.forEach((app) => {
486
- console.log(`${app} ${failedList.indexOf(app) == -1 ? 'removed' : 'not found'}`)
487
- })
488
- }
489
- } else {
490
- console.error(res.data.error)
491
- }
492
- return res
493
- } catch (e) {
494
- const errMsg = (e.response?.data ? e.response.data : e)
495
- console.error(errMsg)
496
- return errMsg
497
- }
498
- })()
499
- })
500
- .recommendCommands()
501
- .demandCommand(1, 'You need at least one command before moving on')
502
- .strict()
503
-
504
- if (process.env.BIZA_APP_SKIP_PARSE !== '1') {
505
- buildCli().parse()
589
+ const buildCli = () =>
590
+ Object.keys(options)
591
+ .reduce(
592
+ (app, optKey) => (app = app.option(optKey, options[optKey])),
593
+ yargs(process.argv.slice(2)),
594
+ )
595
+ .command(
596
+ "add",
597
+ "Add Biz-A Application",
598
+ addCommandOptions,
599
+ async (commandOptions) => {
600
+ await runUnitTests(commandOptions);
601
+ },
602
+ )
603
+ .command(
604
+ "remove",
605
+ "Remove Biz-A Application",
606
+ removeCommandOptions,
607
+ (commandOptions) => {
608
+ (async () => {
609
+ try {
610
+ const baseUrl = "fina/rest/TOrmMethod/%22deleteApp%22";
611
+ const url = commandOptions.sub
612
+ ? `${commandOptions.server}/hub/${baseUrl}?subdomain=${commandOptions.sub}`
613
+ : `${commandOptions.server}:${commandOptions.apiPort}/${baseUrl}`;
614
+ const headers = { "Content-Type": "text/plain" };
615
+ const deleteApps = commandOptions.appName
616
+ .trim()
617
+ .replaceAll(" ", "")
618
+ .toLowerCase();
619
+ const param = {
620
+ _parameters: [commandOptions.dbIndex, deleteApps],
621
+ };
622
+ const res = await axios.post(url, param, {
623
+ headers: headers,
624
+ });
625
+ if (res.data?.success) {
626
+ if (deleteApps == "") {
627
+ console.log("All apps removed");
628
+ } else {
629
+ const failedList =
630
+ res.data._f &&
631
+ typeof res.data._f == "string"
632
+ ? res.data._f
633
+ .trim()
634
+ .replaceAll(" ", "")
635
+ .toLowerCase()
636
+ .split(",")
637
+ : [];
638
+ const removeList = deleteApps.split(",");
639
+ removeList.forEach((app) => {
640
+ console.log(
641
+ `${app} ${failedList.indexOf(app) == -1 ? "removed" : "not found"}`,
642
+ );
643
+ });
644
+ }
645
+ } else {
646
+ console.error(res.data.error);
647
+ }
648
+ return res;
649
+ } catch (e) {
650
+ const errMsg = e.response?.data ? e.response.data : e;
651
+ console.error(errMsg);
652
+ return errMsg;
653
+ }
654
+ })();
655
+ },
656
+ )
657
+ .recommendCommands()
658
+ .demandCommand(1, "You need at least one command before moving on")
659
+ .strict();
660
+
661
+ if (process.env.BIZA_APP_SKIP_PARSE !== "1") {
662
+ buildCli().parse();
506
663
  }
507
664
 
508
- export { options, addCommandOptions, removeCommandOptions, getFileList, addApp }
665
+ export {
666
+ options,
667
+ addCommandOptions,
668
+ removeCommandOptions,
669
+ getFileList,
670
+ addApp,
671
+ };