monocart-reporter 2.9.6 → 2.9.7
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/LICENSE +21 -21
- package/README.md +1180 -1180
- package/lib/cli.js +372 -372
- package/lib/common.js +244 -244
- package/lib/default/columns.js +79 -79
- package/lib/default/options.js +95 -95
- package/lib/default/summary.js +80 -80
- package/lib/default/template.html +47 -47
- package/lib/generate-data.js +174 -174
- package/lib/generate-report.js +360 -360
- package/lib/index.d.ts +268 -268
- package/lib/index.js +253 -253
- package/lib/index.mjs +19 -19
- package/lib/merge-data.js +405 -405
- package/lib/packages/monocart-reporter-assets.js +3 -3
- package/lib/platform/concurrency.js +74 -74
- package/lib/platform/share.js +369 -369
- package/lib/plugins/audit/audit.js +119 -119
- package/lib/plugins/comments.js +124 -124
- package/lib/plugins/coverage/coverage.js +169 -169
- package/lib/plugins/email.js +76 -76
- package/lib/plugins/metadata/metadata.js +25 -25
- package/lib/plugins/network/network.js +186 -186
- package/lib/plugins/state/client.js +152 -152
- package/lib/plugins/state/state.js +194 -194
- package/lib/utils/pie.js +148 -148
- package/lib/utils/system.js +145 -145
- package/lib/utils/util.js +511 -511
- package/lib/visitor.js +915 -915
- package/package.json +89 -89
package/lib/cli.js
CHANGED
|
@@ -1,372 +1,372 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const http = require('http');
|
|
6
|
-
const https = require('https');
|
|
7
|
-
const net = require('net');
|
|
8
|
-
const os = require('os');
|
|
9
|
-
const { pathToFileURL } = require('url');
|
|
10
|
-
const EC = require('eight-colors');
|
|
11
|
-
const KSR = require('koa-static-resolver');
|
|
12
|
-
const Koa = require('koa');
|
|
13
|
-
const CG = require('console-grid');
|
|
14
|
-
|
|
15
|
-
const Util = require('./utils/util.js');
|
|
16
|
-
const {
|
|
17
|
-
program, open, glob, findUpSync, supportsColor
|
|
18
|
-
} = require('./packages/monocart-reporter-vendor.js');
|
|
19
|
-
const getDefaultOptions = require('./default/options.js');
|
|
20
|
-
const merge = require('./merge-data.js');
|
|
21
|
-
const version = require('../package.json').version;
|
|
22
|
-
|
|
23
|
-
// https://github.com/chalk/supports-color
|
|
24
|
-
// disabled color if Terminal stdout does not support color
|
|
25
|
-
if (!supportsColor.stdout) {
|
|
26
|
-
EC.disabled = true;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const getInternalIps = () => {
|
|
31
|
-
const n = os.networkInterfaces();
|
|
32
|
-
// console.log(n);
|
|
33
|
-
const list = [];
|
|
34
|
-
for (const k in n) {
|
|
35
|
-
const inter = n[k];
|
|
36
|
-
for (const j in inter) {
|
|
37
|
-
const item = inter[j];
|
|
38
|
-
if (item.family === 'IPv4' && !item.internal) {
|
|
39
|
-
const a = item.address;
|
|
40
|
-
if (a.startsWith('192.') || a.startsWith('10.')) {
|
|
41
|
-
list.push(a);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return list;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const generatePort = (startPort) => {
|
|
51
|
-
return new Promise((resolve) => {
|
|
52
|
-
const server = net.createServer().listen(startPort);
|
|
53
|
-
server.on('listening', function() {
|
|
54
|
-
server.close();
|
|
55
|
-
resolve(startPort);
|
|
56
|
-
});
|
|
57
|
-
server.on('error', function(err) {
|
|
58
|
-
if (err.code === 'EADDRINUSE') {
|
|
59
|
-
generatePort(startPort + 1).then((port) => {
|
|
60
|
-
resolve(port);
|
|
61
|
-
});
|
|
62
|
-
} else {
|
|
63
|
-
resolve(startPort);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const showIpInfo = (protocol, port) => {
|
|
70
|
-
const ips = getInternalIps();
|
|
71
|
-
CG({
|
|
72
|
-
options: {
|
|
73
|
-
headerVisible: false
|
|
74
|
-
},
|
|
75
|
-
columns: [{
|
|
76
|
-
id: 'type'
|
|
77
|
-
}, {
|
|
78
|
-
id: 'url',
|
|
79
|
-
formatter: (v) => {
|
|
80
|
-
return EC.green(v);
|
|
81
|
-
}
|
|
82
|
-
}],
|
|
83
|
-
rows: [{
|
|
84
|
-
url: `${protocol}://localhost:${port}`,
|
|
85
|
-
type: 'Local'
|
|
86
|
-
}, ... ips.map((ip) => {
|
|
87
|
-
return {
|
|
88
|
-
url: `${protocol}://${ip}:${port}`,
|
|
89
|
-
type: 'Internal'
|
|
90
|
-
};
|
|
91
|
-
})]
|
|
92
|
-
});
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const openUrl = async (p) => {
|
|
96
|
-
await open(p);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const createServer = (app, options) => {
|
|
100
|
-
|
|
101
|
-
if (options.ssl) {
|
|
102
|
-
const [keyPath, certPath] = options.ssl.split(',');
|
|
103
|
-
const serverOptions = {
|
|
104
|
-
key: fs.readFileSync(path.resolve(keyPath)),
|
|
105
|
-
cert: fs.readFileSync(path.resolve(certPath))
|
|
106
|
-
};
|
|
107
|
-
return https.createServer(serverOptions, app.callback());
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return http.createServer(app.callback());
|
|
111
|
-
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const serveReport = async (p, options) => {
|
|
115
|
-
|
|
116
|
-
if (!p) {
|
|
117
|
-
p = getDefaultOptions().outputFile;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const dirs = [];
|
|
121
|
-
let filename = '';
|
|
122
|
-
if (fs.existsSync(p)) {
|
|
123
|
-
const stat = fs.statSync(p);
|
|
124
|
-
if (stat.isDirectory()) {
|
|
125
|
-
dirs.push(p);
|
|
126
|
-
} else if (stat.isFile()) {
|
|
127
|
-
filename = path.basename(p);
|
|
128
|
-
dirs.push(path.dirname(p));
|
|
129
|
-
}
|
|
130
|
-
} else {
|
|
131
|
-
EC.logRed(`The path does not exists: ${p}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
dirs.push('./');
|
|
135
|
-
|
|
136
|
-
console.log('serve dirs', dirs);
|
|
137
|
-
|
|
138
|
-
const app = new Koa();
|
|
139
|
-
app.use(KSR({
|
|
140
|
-
dirs,
|
|
141
|
-
headers: {
|
|
142
|
-
'Access-Control-Allow-Origin': '*'
|
|
143
|
-
},
|
|
144
|
-
gzip: false,
|
|
145
|
-
// max-age=<seconds>
|
|
146
|
-
maxAge: 1
|
|
147
|
-
}));
|
|
148
|
-
|
|
149
|
-
const server = createServer(app, options);
|
|
150
|
-
|
|
151
|
-
const port = await generatePort(8090);
|
|
152
|
-
const protocol = options.ssl ? 'https' : 'http';
|
|
153
|
-
|
|
154
|
-
const url = `${protocol}://localhost:${port}/${filename}`;
|
|
155
|
-
|
|
156
|
-
server.listen(port, function() {
|
|
157
|
-
EC.logCyan(`${new Date().toLocaleString()} server listening on ${url}`);
|
|
158
|
-
if (protocol === 'https') {
|
|
159
|
-
showIpInfo(protocol, port);
|
|
160
|
-
}
|
|
161
|
-
if (options.open) {
|
|
162
|
-
openUrl(url);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const findUpConfig = (customConfigFile) => {
|
|
169
|
-
if (customConfigFile) {
|
|
170
|
-
if (fs.existsSync(customConfigFile)) {
|
|
171
|
-
return customConfigFile;
|
|
172
|
-
}
|
|
173
|
-
// custom config not found
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const defaultConfigList = [
|
|
178
|
-
'mr.config.js',
|
|
179
|
-
'mr.config.cjs',
|
|
180
|
-
'mr.config.mjs',
|
|
181
|
-
'mr.config.json',
|
|
182
|
-
'mr.config.ts'
|
|
183
|
-
];
|
|
184
|
-
|
|
185
|
-
const configPath = findUpSync(defaultConfigList);
|
|
186
|
-
if (configPath) {
|
|
187
|
-
return configPath;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// default config not found
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const checkRegisterFeature = () => {
|
|
194
|
-
const nv = process.versions.node;
|
|
195
|
-
|
|
196
|
-
// "module.register" added in Node.js: v20.6.0
|
|
197
|
-
// if (Util.cmpVersion(nv, '20.6.0') >= 0) {
|
|
198
|
-
// return true;
|
|
199
|
-
// }
|
|
200
|
-
// but also added in: v18.19.0
|
|
201
|
-
const requiredNV = '18.19.0';
|
|
202
|
-
if (Util.cmpVersion(nv, requiredNV) < 0) {
|
|
203
|
-
Util.logInfo(`The current Node.js version "${nv}" does NOT support "module.register", it requires "${requiredNV}" or higher.`);
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// could be < 20.6.0 but just ignore it, please using latest minor version
|
|
208
|
-
|
|
209
|
-
return true;
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const loadEnv = (cliOptions) => {
|
|
213
|
-
if (!cliOptions.env) {
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
const envFile = cliOptions.env === true ? '.env' : cliOptions.env;
|
|
217
|
-
const loadEnvFile = process.loadEnvFile;
|
|
218
|
-
if (typeof loadEnvFile === 'function') {
|
|
219
|
-
loadEnvFile(envFile);
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const initNodeOptions = async (cliOptions) => {
|
|
225
|
-
|
|
226
|
-
loadEnv(cliOptions);
|
|
227
|
-
|
|
228
|
-
const supportRegister = checkRegisterFeature();
|
|
229
|
-
if (!supportRegister) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// for loading mr.config.ts
|
|
234
|
-
const modulePath = cliOptions.import || cliOptions.require;
|
|
235
|
-
if (!modulePath) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const res = await import(modulePath);
|
|
240
|
-
if (res && typeof res.register === 'function') {
|
|
241
|
-
await res.register();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const resolveConfigOptions = async (configPath) => {
|
|
248
|
-
// json format
|
|
249
|
-
const ext = path.extname(configPath);
|
|
250
|
-
if (ext === '.json' || configPath.slice(-2) === 'rc') {
|
|
251
|
-
return JSON.parse(Util.readFileSync(configPath));
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
let configOptions;
|
|
255
|
-
let err;
|
|
256
|
-
try {
|
|
257
|
-
configOptions = await import(pathToFileURL(configPath));
|
|
258
|
-
} catch (ee) {
|
|
259
|
-
err = ee;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (err) {
|
|
263
|
-
Util.logError(`ERROR: failed to load config "${configPath}": ${err && err.message} `);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// could be multiple level default
|
|
268
|
-
while (configOptions && configOptions.default) {
|
|
269
|
-
configOptions = configOptions.default;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return configOptions;
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
const exit = (code) => {
|
|
276
|
-
if (code) {
|
|
277
|
-
process.exit(code);
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const mergeReports = async (str, cliOptions) => {
|
|
282
|
-
|
|
283
|
-
await initNodeOptions(cliOptions);
|
|
284
|
-
|
|
285
|
-
const customConfig = cliOptions.config;
|
|
286
|
-
const configPath = findUpConfig(customConfig);
|
|
287
|
-
|
|
288
|
-
let configOptions = {};
|
|
289
|
-
if (configPath) {
|
|
290
|
-
configOptions = await resolveConfigOptions(configPath);
|
|
291
|
-
} else {
|
|
292
|
-
if (customConfig) {
|
|
293
|
-
Util.logError(`ERROR: not found config file: ${customConfig}`);
|
|
294
|
-
exit(1);
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const options = {
|
|
300
|
-
... getDefaultOptions(),
|
|
301
|
-
... configOptions,
|
|
302
|
-
... cliOptions
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
Util.initLoggingLevel(options.logging);
|
|
306
|
-
|
|
307
|
-
Util.logInfo(`glob patterns: ${EC.cyan(str)}`);
|
|
308
|
-
|
|
309
|
-
const files = await glob(str);
|
|
310
|
-
if (!Util.isList(files)) {
|
|
311
|
-
Util.logError(`ERROR: no files found with glob: ${str}`);
|
|
312
|
-
exit(1);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
Util.logInfo(`glob files: ${files.join(', ')}`);
|
|
317
|
-
|
|
318
|
-
const reportData = await merge(files, options);
|
|
319
|
-
if (!reportData) {
|
|
320
|
-
exit(1);
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
program
|
|
325
|
-
.name('monocart')
|
|
326
|
-
.description('CLI to serve monocart reporter')
|
|
327
|
-
.version(version);
|
|
328
|
-
|
|
329
|
-
program.command('show-report')
|
|
330
|
-
.alias('show')
|
|
331
|
-
.description('Show report')
|
|
332
|
-
.argument('<path-to-report>', 'Report dir or html path')
|
|
333
|
-
.option('-s, --ssl <path-to-key,path-to-cert>', 'Start https server')
|
|
334
|
-
.action((str, options) => {
|
|
335
|
-
options.open = true;
|
|
336
|
-
serveReport(str, options);
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
program.command('serve-report')
|
|
340
|
-
.alias('serve')
|
|
341
|
-
.description('Serve report')
|
|
342
|
-
.argument('<path-to-report>', 'Report dir or html path')
|
|
343
|
-
.option('-s, --ssl <path-to-key,path-to-cert>', 'Start https server')
|
|
344
|
-
.action((str, options) => {
|
|
345
|
-
serveReport(str, options);
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
program.command('merge-reports')
|
|
349
|
-
.alias('merge')
|
|
350
|
-
.description('Merge reports')
|
|
351
|
-
.argument('<path>', 'path to report dirs')
|
|
352
|
-
.option('-c, --config <path>', 'config file')
|
|
353
|
-
|
|
354
|
-
.option('-n, --name <name>', 'report name for title')
|
|
355
|
-
.option('-o, --outputFile <path>', 'output file')
|
|
356
|
-
|
|
357
|
-
.option('--import <module>', 'preload module at startup')
|
|
358
|
-
.option('--require <module>', 'preload module at startup')
|
|
359
|
-
|
|
360
|
-
.option('--env [path]', 'env file (default: ".env")')
|
|
361
|
-
|
|
362
|
-
.action((str, options) => {
|
|
363
|
-
mergeReports(str, options);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
program.addHelpText('after', `
|
|
367
|
-
Starts ${EC.cyan('https')} with option --ssl:
|
|
368
|
-
npx monocart show-report <path-to-report> ${EC.cyan('--ssl <path-to-key,path-to-cert>')}
|
|
369
|
-
# Create and install local CA with 'mkcert', see: https://mkcert.dev
|
|
370
|
-
`);
|
|
371
|
-
|
|
372
|
-
program.parse();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const net = require('net');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const { pathToFileURL } = require('url');
|
|
10
|
+
const EC = require('eight-colors');
|
|
11
|
+
const KSR = require('koa-static-resolver');
|
|
12
|
+
const Koa = require('koa');
|
|
13
|
+
const CG = require('console-grid');
|
|
14
|
+
|
|
15
|
+
const Util = require('./utils/util.js');
|
|
16
|
+
const {
|
|
17
|
+
program, open, glob, findUpSync, supportsColor
|
|
18
|
+
} = require('./packages/monocart-reporter-vendor.js');
|
|
19
|
+
const getDefaultOptions = require('./default/options.js');
|
|
20
|
+
const merge = require('./merge-data.js');
|
|
21
|
+
const version = require('../package.json').version;
|
|
22
|
+
|
|
23
|
+
// https://github.com/chalk/supports-color
|
|
24
|
+
// disabled color if Terminal stdout does not support color
|
|
25
|
+
if (!supportsColor.stdout) {
|
|
26
|
+
EC.disabled = true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
const getInternalIps = () => {
|
|
31
|
+
const n = os.networkInterfaces();
|
|
32
|
+
// console.log(n);
|
|
33
|
+
const list = [];
|
|
34
|
+
for (const k in n) {
|
|
35
|
+
const inter = n[k];
|
|
36
|
+
for (const j in inter) {
|
|
37
|
+
const item = inter[j];
|
|
38
|
+
if (item.family === 'IPv4' && !item.internal) {
|
|
39
|
+
const a = item.address;
|
|
40
|
+
if (a.startsWith('192.') || a.startsWith('10.')) {
|
|
41
|
+
list.push(a);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return list;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
const generatePort = (startPort) => {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const server = net.createServer().listen(startPort);
|
|
53
|
+
server.on('listening', function() {
|
|
54
|
+
server.close();
|
|
55
|
+
resolve(startPort);
|
|
56
|
+
});
|
|
57
|
+
server.on('error', function(err) {
|
|
58
|
+
if (err.code === 'EADDRINUSE') {
|
|
59
|
+
generatePort(startPort + 1).then((port) => {
|
|
60
|
+
resolve(port);
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
resolve(startPort);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const showIpInfo = (protocol, port) => {
|
|
70
|
+
const ips = getInternalIps();
|
|
71
|
+
CG({
|
|
72
|
+
options: {
|
|
73
|
+
headerVisible: false
|
|
74
|
+
},
|
|
75
|
+
columns: [{
|
|
76
|
+
id: 'type'
|
|
77
|
+
}, {
|
|
78
|
+
id: 'url',
|
|
79
|
+
formatter: (v) => {
|
|
80
|
+
return EC.green(v);
|
|
81
|
+
}
|
|
82
|
+
}],
|
|
83
|
+
rows: [{
|
|
84
|
+
url: `${protocol}://localhost:${port}`,
|
|
85
|
+
type: 'Local'
|
|
86
|
+
}, ... ips.map((ip) => {
|
|
87
|
+
return {
|
|
88
|
+
url: `${protocol}://${ip}:${port}`,
|
|
89
|
+
type: 'Internal'
|
|
90
|
+
};
|
|
91
|
+
})]
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const openUrl = async (p) => {
|
|
96
|
+
await open(p);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const createServer = (app, options) => {
|
|
100
|
+
|
|
101
|
+
if (options.ssl) {
|
|
102
|
+
const [keyPath, certPath] = options.ssl.split(',');
|
|
103
|
+
const serverOptions = {
|
|
104
|
+
key: fs.readFileSync(path.resolve(keyPath)),
|
|
105
|
+
cert: fs.readFileSync(path.resolve(certPath))
|
|
106
|
+
};
|
|
107
|
+
return https.createServer(serverOptions, app.callback());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return http.createServer(app.callback());
|
|
111
|
+
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const serveReport = async (p, options) => {
|
|
115
|
+
|
|
116
|
+
if (!p) {
|
|
117
|
+
p = getDefaultOptions().outputFile;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const dirs = [];
|
|
121
|
+
let filename = '';
|
|
122
|
+
if (fs.existsSync(p)) {
|
|
123
|
+
const stat = fs.statSync(p);
|
|
124
|
+
if (stat.isDirectory()) {
|
|
125
|
+
dirs.push(p);
|
|
126
|
+
} else if (stat.isFile()) {
|
|
127
|
+
filename = path.basename(p);
|
|
128
|
+
dirs.push(path.dirname(p));
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
EC.logRed(`The path does not exists: ${p}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
dirs.push('./');
|
|
135
|
+
|
|
136
|
+
console.log('serve dirs', dirs);
|
|
137
|
+
|
|
138
|
+
const app = new Koa();
|
|
139
|
+
app.use(KSR({
|
|
140
|
+
dirs,
|
|
141
|
+
headers: {
|
|
142
|
+
'Access-Control-Allow-Origin': '*'
|
|
143
|
+
},
|
|
144
|
+
gzip: false,
|
|
145
|
+
// max-age=<seconds>
|
|
146
|
+
maxAge: 1
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
const server = createServer(app, options);
|
|
150
|
+
|
|
151
|
+
const port = await generatePort(8090);
|
|
152
|
+
const protocol = options.ssl ? 'https' : 'http';
|
|
153
|
+
|
|
154
|
+
const url = `${protocol}://localhost:${port}/${filename}`;
|
|
155
|
+
|
|
156
|
+
server.listen(port, function() {
|
|
157
|
+
EC.logCyan(`${new Date().toLocaleString()} server listening on ${url}`);
|
|
158
|
+
if (protocol === 'https') {
|
|
159
|
+
showIpInfo(protocol, port);
|
|
160
|
+
}
|
|
161
|
+
if (options.open) {
|
|
162
|
+
openUrl(url);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const findUpConfig = (customConfigFile) => {
|
|
169
|
+
if (customConfigFile) {
|
|
170
|
+
if (fs.existsSync(customConfigFile)) {
|
|
171
|
+
return customConfigFile;
|
|
172
|
+
}
|
|
173
|
+
// custom config not found
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const defaultConfigList = [
|
|
178
|
+
'mr.config.js',
|
|
179
|
+
'mr.config.cjs',
|
|
180
|
+
'mr.config.mjs',
|
|
181
|
+
'mr.config.json',
|
|
182
|
+
'mr.config.ts'
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
const configPath = findUpSync(defaultConfigList);
|
|
186
|
+
if (configPath) {
|
|
187
|
+
return configPath;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// default config not found
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const checkRegisterFeature = () => {
|
|
194
|
+
const nv = process.versions.node;
|
|
195
|
+
|
|
196
|
+
// "module.register" added in Node.js: v20.6.0
|
|
197
|
+
// if (Util.cmpVersion(nv, '20.6.0') >= 0) {
|
|
198
|
+
// return true;
|
|
199
|
+
// }
|
|
200
|
+
// but also added in: v18.19.0
|
|
201
|
+
const requiredNV = '18.19.0';
|
|
202
|
+
if (Util.cmpVersion(nv, requiredNV) < 0) {
|
|
203
|
+
Util.logInfo(`The current Node.js version "${nv}" does NOT support "module.register", it requires "${requiredNV}" or higher.`);
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// could be < 20.6.0 but just ignore it, please using latest minor version
|
|
208
|
+
|
|
209
|
+
return true;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const loadEnv = (cliOptions) => {
|
|
213
|
+
if (!cliOptions.env) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const envFile = cliOptions.env === true ? '.env' : cliOptions.env;
|
|
217
|
+
const loadEnvFile = process.loadEnvFile;
|
|
218
|
+
if (typeof loadEnvFile === 'function') {
|
|
219
|
+
loadEnvFile(envFile);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
const initNodeOptions = async (cliOptions) => {
|
|
225
|
+
|
|
226
|
+
loadEnv(cliOptions);
|
|
227
|
+
|
|
228
|
+
const supportRegister = checkRegisterFeature();
|
|
229
|
+
if (!supportRegister) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// for loading mr.config.ts
|
|
234
|
+
const modulePath = cliOptions.import || cliOptions.require;
|
|
235
|
+
if (!modulePath) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const res = await import(modulePath);
|
|
240
|
+
if (res && typeof res.register === 'function') {
|
|
241
|
+
await res.register();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
const resolveConfigOptions = async (configPath) => {
|
|
248
|
+
// json format
|
|
249
|
+
const ext = path.extname(configPath);
|
|
250
|
+
if (ext === '.json' || configPath.slice(-2) === 'rc') {
|
|
251
|
+
return JSON.parse(Util.readFileSync(configPath));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let configOptions;
|
|
255
|
+
let err;
|
|
256
|
+
try {
|
|
257
|
+
configOptions = await import(pathToFileURL(configPath));
|
|
258
|
+
} catch (ee) {
|
|
259
|
+
err = ee;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (err) {
|
|
263
|
+
Util.logError(`ERROR: failed to load config "${configPath}": ${err && err.message} `);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// could be multiple level default
|
|
268
|
+
while (configOptions && configOptions.default) {
|
|
269
|
+
configOptions = configOptions.default;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return configOptions;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const exit = (code) => {
|
|
276
|
+
if (code) {
|
|
277
|
+
process.exit(code);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const mergeReports = async (str, cliOptions) => {
|
|
282
|
+
|
|
283
|
+
await initNodeOptions(cliOptions);
|
|
284
|
+
|
|
285
|
+
const customConfig = cliOptions.config;
|
|
286
|
+
const configPath = findUpConfig(customConfig);
|
|
287
|
+
|
|
288
|
+
let configOptions = {};
|
|
289
|
+
if (configPath) {
|
|
290
|
+
configOptions = await resolveConfigOptions(configPath);
|
|
291
|
+
} else {
|
|
292
|
+
if (customConfig) {
|
|
293
|
+
Util.logError(`ERROR: not found config file: ${customConfig}`);
|
|
294
|
+
exit(1);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const options = {
|
|
300
|
+
... getDefaultOptions(),
|
|
301
|
+
... configOptions,
|
|
302
|
+
... cliOptions
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
Util.initLoggingLevel(options.logging);
|
|
306
|
+
|
|
307
|
+
Util.logInfo(`glob patterns: ${EC.cyan(str)}`);
|
|
308
|
+
|
|
309
|
+
const files = await glob(str);
|
|
310
|
+
if (!Util.isList(files)) {
|
|
311
|
+
Util.logError(`ERROR: no files found with glob: ${str}`);
|
|
312
|
+
exit(1);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
Util.logInfo(`glob files: ${files.join(', ')}`);
|
|
317
|
+
|
|
318
|
+
const reportData = await merge(files, options);
|
|
319
|
+
if (!reportData) {
|
|
320
|
+
exit(1);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
program
|
|
325
|
+
.name('monocart')
|
|
326
|
+
.description('CLI to serve monocart reporter')
|
|
327
|
+
.version(version);
|
|
328
|
+
|
|
329
|
+
program.command('show-report')
|
|
330
|
+
.alias('show')
|
|
331
|
+
.description('Show report')
|
|
332
|
+
.argument('<path-to-report>', 'Report dir or html path')
|
|
333
|
+
.option('-s, --ssl <path-to-key,path-to-cert>', 'Start https server')
|
|
334
|
+
.action((str, options) => {
|
|
335
|
+
options.open = true;
|
|
336
|
+
serveReport(str, options);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
program.command('serve-report')
|
|
340
|
+
.alias('serve')
|
|
341
|
+
.description('Serve report')
|
|
342
|
+
.argument('<path-to-report>', 'Report dir or html path')
|
|
343
|
+
.option('-s, --ssl <path-to-key,path-to-cert>', 'Start https server')
|
|
344
|
+
.action((str, options) => {
|
|
345
|
+
serveReport(str, options);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
program.command('merge-reports')
|
|
349
|
+
.alias('merge')
|
|
350
|
+
.description('Merge reports')
|
|
351
|
+
.argument('<path>', 'path to report dirs')
|
|
352
|
+
.option('-c, --config <path>', 'config file')
|
|
353
|
+
|
|
354
|
+
.option('-n, --name <name>', 'report name for title')
|
|
355
|
+
.option('-o, --outputFile <path>', 'output file')
|
|
356
|
+
|
|
357
|
+
.option('--import <module>', 'preload module at startup')
|
|
358
|
+
.option('--require <module>', 'preload module at startup')
|
|
359
|
+
|
|
360
|
+
.option('--env [path]', 'env file (default: ".env")')
|
|
361
|
+
|
|
362
|
+
.action((str, options) => {
|
|
363
|
+
mergeReports(str, options);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
program.addHelpText('after', `
|
|
367
|
+
Starts ${EC.cyan('https')} with option --ssl:
|
|
368
|
+
npx monocart show-report <path-to-report> ${EC.cyan('--ssl <path-to-key,path-to-cert>')}
|
|
369
|
+
# Create and install local CA with 'mkcert', see: https://mkcert.dev
|
|
370
|
+
`);
|
|
371
|
+
|
|
372
|
+
program.parse();
|