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/.editorconfig +16 -0
- package/bin/app.js +638 -475
- package/bin/hub.js +185 -134
- package/bin/hubEvent.js +410 -335
- package/log/debug.log +1 -0
- package/log/error.log +2 -0
- package/log/exception.log +3 -0
- package/log/info.log +1 -0
- package/package.json +71 -70
- package/tests/app.test.js +1096 -708
- package/tests/hubPublish.test.js +218 -207
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 {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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 = () =>
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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 {
|
|
665
|
+
export {
|
|
666
|
+
options,
|
|
667
|
+
addCommandOptions,
|
|
668
|
+
removeCommandOptions,
|
|
669
|
+
getFileList,
|
|
670
|
+
addApp,
|
|
671
|
+
};
|