biz-a-cli 2.3.70 → 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/.editorconfig +16 -0
- package/bin/app.js +659 -256
- package/bin/hub.js +185 -134
- package/bin/hubEvent.js +412 -284
- package/bin/log/debug.log +12 -0
- package/bin/log/error.log +12 -0
- package/bin/log/exception.log +6 -0
- package/bin/log/info.log +12 -0
- package/log/debug.log +181 -0
- package/log/error.log +7 -0
- package/log/exception.log +3 -0
- package/log/info.log +6 -0
- package/package.json +70 -71
- package/tests/app.test.js +1093 -588
- package/tests/hubPublish.test.js +231 -0
package/bin/app.js
CHANGED
|
@@ -1,268 +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 {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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";
|
|
17
|
+
|
|
18
|
+
const 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();
|
|
14
32
|
|
|
15
33
|
const options = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
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
|
+
};
|
|
44
62
|
|
|
45
63
|
const addCommandOptions = {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
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
|
+
};
|
|
61
79
|
|
|
62
80
|
const removeCommandOptions = {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
n: {
|
|
82
|
+
alias: "appName",
|
|
83
|
+
describe: "Application name",
|
|
84
|
+
type: "string",
|
|
85
|
+
demandOption: true,
|
|
86
|
+
default: "",
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const prepareKeys = async () => {
|
|
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
|
+
};
|
|
144
|
+
|
|
145
|
+
const compressIt = (fileName, folderPath) => {
|
|
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
|
+
};
|
|
179
|
+
|
|
180
|
+
const getNormalizedFileName = (file) => {
|
|
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
|
+
};
|
|
190
|
+
|
|
191
|
+
const normalizeFileList = (files) => {
|
|
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();
|
|
212
|
+
|
|
213
|
+
const normalizeBodyScripts = (body = null) => {
|
|
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
|
+
};
|
|
261
|
+
|
|
262
|
+
const getFileList = ({ workingDir = process.cwd(), files = null } = {}) => {
|
|
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
|
+
};
|
|
288
|
+
|
|
289
|
+
const parseApplicationConfigData = (rawConfig) => {
|
|
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
|
+
};
|
|
380
|
+
|
|
381
|
+
async function addApp({
|
|
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,
|
|
390
|
+
} = {}) {
|
|
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
|
+
}
|
|
70
525
|
}
|
|
71
526
|
|
|
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
|
-
tar.c({ file: fileName, cwd: folderPath, gzip: { level: 9 }, strict: true, sync: true }, fs.readdirSync(folderPath))
|
|
103
|
-
compressSuccess = true
|
|
104
|
-
tar.t({
|
|
105
|
-
file: fileName, cwd: folderPath, sync: true, onentry: (entry) => {
|
|
106
|
-
if (entry.size == 0) {
|
|
107
|
-
compressSuccess = false
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
if (compressSuccess == false) {
|
|
112
|
-
fs.unlinkSync(fileName)
|
|
113
|
-
retryCount++
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
while ((compressSuccess == false) && (retryCount <= maxRetry))
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const getFileList = () => {
|
|
120
|
-
return (fs.readdirSync(process.cwd())).filter((fileName) => {
|
|
121
|
-
const stat = fs.statSync(fileName)
|
|
122
|
-
return stat.isFile() && (stat.size > 0) && ((fileName.split('.').pop().toLowerCase().match(/^(js)$/) || (fileName.toLowerCase() == 'menu.json')))
|
|
123
|
-
})
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function runUnitTests() { //SCY BZ 4331
|
|
127
|
-
let jestCommand = ['--no-install', 'jest', '--json'];
|
|
128
|
-
let output = '';
|
|
129
|
-
|
|
130
|
-
const testDir = path.join(options.workingDir, "test");
|
|
131
|
-
const workDir = path.resolve(options.workingDir);
|
|
132
|
-
try {
|
|
133
|
-
process.chdir(testDir)
|
|
134
|
-
} catch (error) {
|
|
135
|
-
if (error.code === 'ENOENT') {
|
|
136
|
-
jestCommand.push('--passWithNoTests')
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
process.chdir(workDir);
|
|
140
|
-
|
|
141
|
-
const child = process.platform === 'win32'
|
|
142
|
-
? spawn('cmd.exe', ['/d', '/s', '/c', 'npx', ...jestCommand])
|
|
143
|
-
: spawn('npx', jestCommand)
|
|
144
|
-
const collectOutput = data => {
|
|
145
|
-
output += data?.toString() || ''
|
|
146
|
-
}
|
|
147
|
-
child.stderr?.on('data', collectOutput)
|
|
148
|
-
child.stdout?.on('data', () => { }) // SCY BZ 4363, ref : https://nodejs.org/download/release/v22.19.0/docs/api/child_process.html
|
|
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');
|
|
167
|
-
}
|
|
168
|
-
})
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async function addApp() {
|
|
172
|
-
/*
|
|
173
|
-
hex => 2 char = 1 bytes => can be encrypted
|
|
174
|
-
base64 => 4 char = 3 bytes => can be encrypted. Smaller size compare to Hex
|
|
175
|
-
utf8 => 1 char = 1 - 4 bytes => can not be encrypted, encryption need precise bytes per Character. Smallest Size compare to Hex and base64
|
|
176
|
-
*/
|
|
177
|
-
try {
|
|
178
|
-
const bundlingStart = performance.now()
|
|
179
|
-
const rootFolder = './upload/'
|
|
180
|
-
const bundleName = basename(process.cwd()).trim().replace(' ', '').toLowerCase()
|
|
181
|
-
const bundleFolder = rootFolder + bundleName + '/'
|
|
182
|
-
const files = getFileList()
|
|
183
|
-
if (files.length > 0) {
|
|
184
|
-
const keys = await prepareKeys()
|
|
185
|
-
let processedFile = 0
|
|
186
|
-
fs.rmSync(rootFolder, { force: true, recursive: true })
|
|
187
|
-
fs.mkdirSync(bundleFolder, { recursive: true })
|
|
188
|
-
for (const file of files) {
|
|
189
|
-
const fileName = file.toLowerCase();
|
|
190
|
-
const encryptedScript = encryptScript(await prepareScript(fileName, fs.readFileSync(fileName).toString(), options.verbose), keys.encryptKey)
|
|
191
|
-
if (fileName == 'menu.json') {
|
|
192
|
-
keys.metadata['menu'] = encryptedScript.toString('base64')
|
|
193
|
-
} else {
|
|
194
|
-
fs.writeFileSync(bundleFolder + fileName, encryptedScript.toString('base64'))
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
processedFile++
|
|
198
|
-
}
|
|
199
|
-
const bundleFile = `${rootFolder}${bundleName}.tgz`
|
|
200
|
-
compressIt(bundleFile, bundleFolder)
|
|
201
|
-
console.log(`Finished packing ${processedFile} files into "${bundleFile}" (${((performance.now() - bundlingStart) / 1000).toFixed(2)}s)`)
|
|
202
|
-
|
|
203
|
-
// send to API
|
|
204
|
-
const uploadingStart = performance.now()
|
|
205
|
-
const data = (fs.readFileSync(bundleFile)).toString('base64') // *.tgz to base64String
|
|
206
|
-
const baseUrl = 'fina/rest/TOrmMethod/%22setApp%22'
|
|
207
|
-
const url = options.sub ?
|
|
208
|
-
`${options.server}/hub/${baseUrl}?subdomain=${options.sub}` :
|
|
209
|
-
`${options.server}:${options.apiPort}/${baseUrl}`
|
|
210
|
-
const headers = { 'Content-Type': 'text/plain' }
|
|
211
|
-
const param = { _parameters: [options.dbIndex, bundleName, data, keys.metadata] }
|
|
212
|
-
const res = await axios.post(url, param, { headers: headers });
|
|
213
|
-
if (res.data.success) {
|
|
214
|
-
console.log(`Finished uploading "${bundleFile}" (${((performance.now() - uploadingStart) / 1000).toFixed(2)}s)`)
|
|
215
|
-
fs.rmSync(rootFolder, { force: true, recursive: true })
|
|
216
|
-
} else {
|
|
217
|
-
console.error(res.data.error)
|
|
218
|
-
}
|
|
219
|
-
} else {
|
|
220
|
-
console.error('Nothing to upload. Please recheck your app folder.')
|
|
221
|
-
}
|
|
222
|
-
} catch (e) {
|
|
223
|
-
console.error(e.response?.data ? e.response.data : e)
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
await runUnitTests()
|
|
228
|
-
})
|
|
229
|
-
.command('remove', 'Remove Biz-A Application', removeCommandOptions, (options) => {
|
|
230
|
-
(async () => {
|
|
231
|
-
try {
|
|
232
|
-
const baseUrl = 'fina/rest/TOrmMethod/%22deleteApp%22'
|
|
233
|
-
const url = options.sub ?
|
|
234
|
-
`${options.server}/hub/${baseUrl}?subdomain=${options.sub}` :
|
|
235
|
-
`${options.server}:${options.apiPort}/${baseUrl}`
|
|
236
|
-
const headers = { 'Content-Type': 'text/plain' }
|
|
237
|
-
const deleteApps = options.appName.trim().replaceAll(' ', '').toLowerCase()
|
|
238
|
-
const param = { _parameters: [options.dbIndex, deleteApps] }
|
|
239
|
-
const res = await axios.post(url, param, { headers: headers });
|
|
240
|
-
if (res.data?.success) {
|
|
241
|
-
if (deleteApps == '') {
|
|
242
|
-
console.log('All apps removed')
|
|
243
|
-
} else {
|
|
244
|
-
const failedList = (res.data._f && (typeof res.data._f == 'string')) ? res.data._f.trim().replaceAll(' ', '').toLowerCase().split(',') : []
|
|
245
|
-
const removeList = deleteApps.split(',')
|
|
246
|
-
removeList.forEach((app) => {
|
|
247
|
-
console.log(`${app} ${failedList.indexOf(app) == -1 ? 'removed' : 'not found'}`)
|
|
248
|
-
})
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
console.error(res.data.error)
|
|
252
|
-
}
|
|
253
|
-
return res
|
|
254
|
-
} catch (e) {
|
|
255
|
-
const errMsg = (e.response?.data ? e.response.data : e)
|
|
256
|
-
console.error(errMsg)
|
|
257
|
-
return errMsg
|
|
258
|
-
}
|
|
259
|
-
})()
|
|
260
|
-
})
|
|
261
|
-
.recommendCommands()
|
|
262
|
-
.demandCommand(1, 'You need at least one command before moving on')
|
|
263
|
-
.strict()
|
|
264
|
-
.parse();
|
|
265
|
-
|
|
266
|
-
export { options, addCommandOptions, removeCommandOptions }
|
|
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
|
+
});
|
|
267
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
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
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();
|
|
663
|
+
}
|
|
268
664
|
|
|
665
|
+
export {
|
|
666
|
+
options,
|
|
667
|
+
addCommandOptions,
|
|
668
|
+
removeCommandOptions,
|
|
669
|
+
getFileList,
|
|
670
|
+
addApp,
|
|
671
|
+
};
|