ortoni-report 4.0.1-beta.0 → 4.0.1-beta.3
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/dist/chunk-4RZ5C7KY.mjs +744 -0
- package/dist/{chunk-MPZLDOCN.mjs → chunk-HOOC3MDY.mjs} +305 -304
- package/dist/chunk-ISCRDMPY.mjs +693 -0
- package/dist/{chunk-INS3E7E6.mjs → chunk-P72FKFLZ.mjs} +352 -298
- package/dist/chunk-S45BZGXX.mjs +744 -0
- package/dist/{chunk-OZS6QIJS.mjs → chunk-ZG4JPYLC.mjs} +352 -298
- package/dist/{chunk-TI33PMMQ.mjs → chunk-ZSPTPISU.mjs} +352 -299
- package/dist/{cli/cli.js → cli.js} +403 -189
- package/dist/cli.mjs +206 -0
- package/dist/index.html +2 -2
- package/dist/ortoni-report.d.mts +7 -0
- package/dist/ortoni-report.d.ts +7 -0
- package/dist/ortoni-report.js +189 -72
- package/dist/ortoni-report.mjs +9 -5
- package/package.json +11 -4
- package/dist/chunk-45EJSEX2.mjs +0 -632
- package/dist/chunk-75EAJL2U.mjs +0 -632
- package/dist/chunk-A6HCKATU.mjs +0 -76
- package/dist/chunk-FGIYOFIC.mjs +0 -632
- package/dist/chunk-FHKWBHU6.mjs +0 -633
- package/dist/chunk-GLICR3VS.mjs +0 -637
- package/dist/chunk-HFO6XSKC.mjs +0 -633
- package/dist/chunk-HOZD6YIV.mjs +0 -634
- package/dist/chunk-IJO2YIFE.mjs +0 -637
- package/dist/chunk-JEIWNUQY.mjs +0 -632
- package/dist/chunk-JPLAGYR7.mjs +0 -632
- package/dist/chunk-NM6ULN2O.mjs +0 -632
- package/dist/chunk-P57227VN.mjs +0 -633
- package/dist/chunk-QMTRYN5N.js +0 -635
- package/dist/chunk-Z5NBP5TS.mjs +0 -635
- package/dist/cli/cli.cjs +0 -678
- package/dist/cli/cli.d.cts +0 -1
- package/dist/cli/cli.mjs +0 -103
- package/dist/ortoni-report.cjs +0 -2134
- package/dist/ortoni-report.d.cts +0 -111
- /package/dist/{cli/cli.d.mts → cli.d.mts} +0 -0
- /package/dist/{cli/cli.d.ts → cli.d.ts} +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
2
|
var __create = Object.create;
|
|
4
3
|
var __defProp = Object.defineProperty;
|
|
5
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -23,76 +22,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
22
|
mod
|
|
24
23
|
));
|
|
25
24
|
|
|
26
|
-
// src/cli
|
|
25
|
+
// src/cli.ts
|
|
27
26
|
var import_commander = require("commander");
|
|
27
|
+
var fs4 = __toESM(require("fs"));
|
|
28
|
+
var path5 = __toESM(require("path"));
|
|
28
29
|
|
|
29
|
-
// src/
|
|
30
|
-
var import_express = __toESM(require("express"));
|
|
31
|
-
var import_path = __toESM(require("path"));
|
|
32
|
-
var import_child_process = require("child_process");
|
|
33
|
-
function startReportServer(reportFolder, reportFilename, port = 2004, open2) {
|
|
34
|
-
const app = (0, import_express.default)();
|
|
35
|
-
app.use(import_express.default.static(reportFolder));
|
|
36
|
-
app.get("/", (_req, res) => {
|
|
37
|
-
try {
|
|
38
|
-
res.sendFile(import_path.default.resolve(reportFolder, reportFilename));
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.error("Ortoni Report: Error sending report file:", error);
|
|
41
|
-
res.status(500).send("Error loading report");
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
try {
|
|
45
|
-
const server = app.listen(port, () => {
|
|
46
|
-
console.log(
|
|
47
|
-
`Server is running at http://localhost:${port}
|
|
48
|
-
Press Ctrl+C to stop.`
|
|
49
|
-
);
|
|
50
|
-
if (open2 === "always" || open2 === "on-failure") {
|
|
51
|
-
try {
|
|
52
|
-
openBrowser(`http://localhost:${port}`);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.error("Ortoni Report: Error opening browser:", error);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
server.on("error", (error) => {
|
|
59
|
-
if (error.code === "EADDRINUSE") {
|
|
60
|
-
console.error(
|
|
61
|
-
`Ortoni Report: Port ${port} is already in use. Trying a different port...`
|
|
62
|
-
);
|
|
63
|
-
} else {
|
|
64
|
-
console.error("Ortoni Report: Server error:", error);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error("Ortoni Report: Error starting the server:", error);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
function openBrowser(url) {
|
|
72
|
-
const platform = process.platform;
|
|
73
|
-
let command;
|
|
74
|
-
try {
|
|
75
|
-
if (platform === "win32") {
|
|
76
|
-
command = "cmd";
|
|
77
|
-
(0, import_child_process.spawn)(command, ["/c", "start", url]);
|
|
78
|
-
} else if (platform === "darwin") {
|
|
79
|
-
command = "open";
|
|
80
|
-
(0, import_child_process.spawn)(command, [url]);
|
|
81
|
-
} else {
|
|
82
|
-
command = "xdg-open";
|
|
83
|
-
(0, import_child_process.spawn)(command, [url]);
|
|
84
|
-
}
|
|
85
|
-
} catch (error) {
|
|
86
|
-
console.error("Ortoni Report: Error opening the browser:", error);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// src/cli/cli.ts
|
|
30
|
+
// src/mergeData.ts
|
|
91
31
|
var fs3 = __toESM(require("fs"));
|
|
92
|
-
var path4 = __toESM(require("path"));
|
|
93
|
-
|
|
94
|
-
// src/cli/mergeData.ts
|
|
95
|
-
var fs2 = __toESM(require("fs"));
|
|
96
32
|
var path3 = __toESM(require("path"));
|
|
97
33
|
|
|
98
34
|
// src/helpers/databaseManager.ts
|
|
@@ -387,74 +323,6 @@ var DatabaseManager = class {
|
|
|
387
323
|
}
|
|
388
324
|
};
|
|
389
325
|
|
|
390
|
-
// src/helpers/fileManager.ts
|
|
391
|
-
var import_fs = __toESM(require("fs"));
|
|
392
|
-
var import_path2 = __toESM(require("path"));
|
|
393
|
-
var FileManager = class {
|
|
394
|
-
constructor(folderPath) {
|
|
395
|
-
this.folderPath = folderPath;
|
|
396
|
-
}
|
|
397
|
-
ensureReportDirectory() {
|
|
398
|
-
const ortoniDataFolder = import_path2.default.join(this.folderPath, "ortoni-data");
|
|
399
|
-
if (!import_fs.default.existsSync(this.folderPath)) {
|
|
400
|
-
import_fs.default.mkdirSync(this.folderPath, { recursive: true });
|
|
401
|
-
} else {
|
|
402
|
-
if (import_fs.default.existsSync(ortoniDataFolder)) {
|
|
403
|
-
import_fs.default.rmSync(ortoniDataFolder, { recursive: true, force: true });
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
writeReportFile(filename, data) {
|
|
408
|
-
const templatePath = import_path2.default.join(__dirname, "..", "index.html");
|
|
409
|
-
let html = import_fs.default.readFileSync(templatePath, "utf-8");
|
|
410
|
-
const reportJSON = JSON.stringify({
|
|
411
|
-
data
|
|
412
|
-
});
|
|
413
|
-
html = html.replace("__ORTONI_TEST_REPORTDATA__", reportJSON);
|
|
414
|
-
import_fs.default.writeFileSync(filename, html);
|
|
415
|
-
return filename;
|
|
416
|
-
}
|
|
417
|
-
writeRawFile(filename, data) {
|
|
418
|
-
const outputPath = import_path2.default.join(process.cwd(), this.folderPath, filename);
|
|
419
|
-
import_fs.default.mkdirSync(import_path2.default.dirname(outputPath), { recursive: true });
|
|
420
|
-
const content = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
421
|
-
import_fs.default.writeFileSync(outputPath, content, "utf-8");
|
|
422
|
-
return outputPath;
|
|
423
|
-
}
|
|
424
|
-
copyTraceViewerAssets(skip) {
|
|
425
|
-
if (skip) return;
|
|
426
|
-
const traceViewerFolder = import_path2.default.join(
|
|
427
|
-
require.resolve("playwright-core"),
|
|
428
|
-
"..",
|
|
429
|
-
"lib",
|
|
430
|
-
"vite",
|
|
431
|
-
"traceViewer"
|
|
432
|
-
);
|
|
433
|
-
const traceViewerTargetFolder = import_path2.default.join(this.folderPath, "trace");
|
|
434
|
-
const traceViewerAssetsTargetFolder = import_path2.default.join(
|
|
435
|
-
traceViewerTargetFolder,
|
|
436
|
-
"assets"
|
|
437
|
-
);
|
|
438
|
-
import_fs.default.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true });
|
|
439
|
-
for (const file of import_fs.default.readdirSync(traceViewerFolder)) {
|
|
440
|
-
if (file.endsWith(".map") || file.includes("watch") || file.includes("assets"))
|
|
441
|
-
continue;
|
|
442
|
-
import_fs.default.copyFileSync(
|
|
443
|
-
import_path2.default.join(traceViewerFolder, file),
|
|
444
|
-
import_path2.default.join(traceViewerTargetFolder, file)
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
const assetsFolder = import_path2.default.join(traceViewerFolder, "assets");
|
|
448
|
-
for (const file of import_fs.default.readdirSync(assetsFolder)) {
|
|
449
|
-
if (file.endsWith(".map") || file.includes("xtermModule")) continue;
|
|
450
|
-
import_fs.default.copyFileSync(
|
|
451
|
-
import_path2.default.join(assetsFolder, file),
|
|
452
|
-
import_path2.default.join(traceViewerAssetsTargetFolder, file)
|
|
453
|
-
);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
|
|
458
326
|
// src/utils/groupProjects.ts
|
|
459
327
|
function groupResults(config, results) {
|
|
460
328
|
if (config.showProject) {
|
|
@@ -498,13 +366,46 @@ var HTMLGenerator = class {
|
|
|
498
366
|
);
|
|
499
367
|
return data;
|
|
500
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Return safe analytics/report data.
|
|
371
|
+
* If no dbManager is provided, return empty defaults and a note explaining why.
|
|
372
|
+
*/
|
|
501
373
|
async getReportData() {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
374
|
+
if (!this.dbManager) {
|
|
375
|
+
return {
|
|
376
|
+
summary: {},
|
|
377
|
+
trends: {},
|
|
378
|
+
flakyTests: [],
|
|
379
|
+
slowTests: [],
|
|
380
|
+
note: "Test history/trends are unavailable (saveHistory disabled or DB not initialized)."
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
const [summary, trends, flakyTests, slowTests] = await Promise.all([
|
|
385
|
+
this.dbManager.getSummaryData ? this.dbManager.getSummaryData() : Promise.resolve({}),
|
|
386
|
+
this.dbManager.getTrends ? this.dbManager.getTrends() : Promise.resolve({}),
|
|
387
|
+
this.dbManager.getFlakyTests ? this.dbManager.getFlakyTests() : Promise.resolve([]),
|
|
388
|
+
this.dbManager.getSlowTests ? this.dbManager.getSlowTests() : Promise.resolve([])
|
|
389
|
+
]);
|
|
390
|
+
return {
|
|
391
|
+
summary: summary ?? {},
|
|
392
|
+
trends: trends ?? {},
|
|
393
|
+
flakyTests: flakyTests ?? [],
|
|
394
|
+
slowTests: slowTests ?? []
|
|
395
|
+
};
|
|
396
|
+
} catch (err) {
|
|
397
|
+
console.warn(
|
|
398
|
+
"HTMLGenerator: failed to read analytics from DB, continuing without history.",
|
|
399
|
+
err
|
|
400
|
+
);
|
|
401
|
+
return {
|
|
402
|
+
summary: {},
|
|
403
|
+
trends: {},
|
|
404
|
+
flakyTests: [],
|
|
405
|
+
slowTests: [],
|
|
406
|
+
note: "Test history/trends could not be loaded due to a DB error."
|
|
407
|
+
};
|
|
408
|
+
}
|
|
508
409
|
}
|
|
509
410
|
async prepareReportData(filteredResults, totalDuration, results, projectSet) {
|
|
510
411
|
const totalTests = filteredResults.length;
|
|
@@ -513,10 +414,10 @@ var HTMLGenerator = class {
|
|
|
513
414
|
const failed = filteredResults.filter(
|
|
514
415
|
(r) => r.status === "failed" || r.status === "timedOut"
|
|
515
416
|
).length;
|
|
516
|
-
const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
|
|
417
|
+
const successRate = totalTests === 0 ? "0.00" : ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
|
|
517
418
|
const allTags = /* @__PURE__ */ new Set();
|
|
518
419
|
results.forEach(
|
|
519
|
-
(result) => result.testTags.forEach((tag) => allTags.add(tag))
|
|
420
|
+
(result) => (result.testTags || []).forEach((tag) => allTags.add(tag))
|
|
520
421
|
);
|
|
521
422
|
const projectResults = this.calculateProjectResults(
|
|
522
423
|
filteredResults,
|
|
@@ -527,13 +428,31 @@ var HTMLGenerator = class {
|
|
|
527
428
|
const testHistories = await Promise.all(
|
|
528
429
|
results.map(async (result) => {
|
|
529
430
|
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
431
|
+
if (!this.dbManager || !this.dbManager.getTestHistory) {
|
|
432
|
+
return {
|
|
433
|
+
testId,
|
|
434
|
+
history: []
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
const history = await this.dbManager.getTestHistory(testId);
|
|
439
|
+
return {
|
|
440
|
+
testId,
|
|
441
|
+
history: history ?? []
|
|
442
|
+
};
|
|
443
|
+
} catch (err) {
|
|
444
|
+
console.warn(
|
|
445
|
+
`HTMLGenerator: failed to read history for ${testId}`,
|
|
446
|
+
err
|
|
447
|
+
);
|
|
448
|
+
return {
|
|
449
|
+
testId,
|
|
450
|
+
history: []
|
|
451
|
+
};
|
|
452
|
+
}
|
|
535
453
|
})
|
|
536
454
|
);
|
|
455
|
+
const reportData = await this.getReportData();
|
|
537
456
|
return {
|
|
538
457
|
summary: {
|
|
539
458
|
overAllResult: {
|
|
@@ -569,7 +488,7 @@ var HTMLGenerator = class {
|
|
|
569
488
|
showProject: this.ortoniConfig.showProject || false
|
|
570
489
|
},
|
|
571
490
|
analytics: {
|
|
572
|
-
reportData
|
|
491
|
+
reportData
|
|
573
492
|
}
|
|
574
493
|
};
|
|
575
494
|
}
|
|
@@ -607,45 +526,275 @@ var HTMLGenerator = class {
|
|
|
607
526
|
}
|
|
608
527
|
};
|
|
609
528
|
|
|
610
|
-
// src/
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
529
|
+
// src/helpers/fileManager.ts
|
|
530
|
+
var import_fs2 = __toESM(require("fs"));
|
|
531
|
+
var import_path2 = __toESM(require("path"));
|
|
532
|
+
|
|
533
|
+
// src/helpers/templateLoader.ts
|
|
534
|
+
var import_fs = __toESM(require("fs"));
|
|
535
|
+
var import_path = __toESM(require("path"));
|
|
536
|
+
var import_meta = {};
|
|
537
|
+
async function readBundledTemplate(pkgName = "ortoni-report") {
|
|
538
|
+
const packagedRel = "dist/index.html";
|
|
539
|
+
try {
|
|
540
|
+
if (typeof require === "function") {
|
|
541
|
+
const resolved = require.resolve(`${pkgName}/${packagedRel}`);
|
|
542
|
+
if (import_fs.default.existsSync(resolved)) {
|
|
543
|
+
return import_fs.default.readFileSync(resolved, "utf-8");
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
try {
|
|
549
|
+
const moduleNS = await import("module");
|
|
550
|
+
if (moduleNS && typeof moduleNS.createRequire === "function") {
|
|
551
|
+
const createRequire = moduleNS.createRequire;
|
|
552
|
+
const req = createRequire(
|
|
553
|
+
// @ts-ignore
|
|
554
|
+
typeof __filename !== "undefined" ? __filename : import_meta.url
|
|
555
|
+
);
|
|
556
|
+
const resolved = req.resolve(`${pkgName}/${packagedRel}`);
|
|
557
|
+
if (import_fs.default.existsSync(resolved)) {
|
|
558
|
+
return import_fs.default.readFileSync(resolved, "utf-8");
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
const here = import_path.default.resolve(__dirname, "../dist/index.html");
|
|
565
|
+
if (import_fs.default.existsSync(here)) return import_fs.default.readFileSync(here, "utf-8");
|
|
566
|
+
} catch {
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
const nm = import_path.default.join(process.cwd(), "node_modules", pkgName, packagedRel);
|
|
570
|
+
if (import_fs.default.existsSync(nm)) return import_fs.default.readFileSync(nm, "utf-8");
|
|
571
|
+
} catch {
|
|
572
|
+
}
|
|
573
|
+
try {
|
|
574
|
+
const alt = import_path.default.join(process.cwd(), "dist", "index.html");
|
|
575
|
+
if (import_fs.default.existsSync(alt)) return import_fs.default.readFileSync(alt, "utf-8");
|
|
576
|
+
} catch {
|
|
577
|
+
}
|
|
578
|
+
throw new Error(
|
|
579
|
+
`ortoni-report template not found (tried:
|
|
580
|
+
- require.resolve('${pkgName}/${packagedRel}')
|
|
581
|
+
- import('module').createRequire(...).resolve('${pkgName}/${packagedRel}')
|
|
582
|
+
- relative ../dist/index.html
|
|
583
|
+
- ${import_path.default.join(
|
|
584
|
+
process.cwd(),
|
|
585
|
+
"node_modules",
|
|
586
|
+
pkgName,
|
|
587
|
+
packagedRel
|
|
588
|
+
)}
|
|
589
|
+
- ${import_path.default.join(process.cwd(), "dist", "index.html")}
|
|
590
|
+
Ensure 'dist/index.html' is present in the published package and package.json 'files' includes 'dist/'.`
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/helpers/fileManager.ts
|
|
595
|
+
var FileManager = class {
|
|
596
|
+
constructor(folderPath) {
|
|
597
|
+
this.folderPath = folderPath;
|
|
598
|
+
}
|
|
599
|
+
ensureReportDirectory() {
|
|
600
|
+
const ortoniDataFolder = import_path2.default.join(this.folderPath, "ortoni-data");
|
|
601
|
+
if (!import_fs2.default.existsSync(this.folderPath)) {
|
|
602
|
+
import_fs2.default.mkdirSync(this.folderPath, { recursive: true });
|
|
603
|
+
} else {
|
|
604
|
+
if (import_fs2.default.existsSync(ortoniDataFolder)) {
|
|
605
|
+
import_fs2.default.rmSync(ortoniDataFolder, { recursive: true, force: true });
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
async writeReportFile(filename, data) {
|
|
610
|
+
let html = await readBundledTemplate();
|
|
611
|
+
const reportJSON = JSON.stringify({
|
|
612
|
+
data
|
|
613
|
+
});
|
|
614
|
+
html = html.replace("__ORTONI_TEST_REPORTDATA__", reportJSON);
|
|
615
|
+
const outputPath = import_path2.default.join(process.cwd(), this.folderPath, filename);
|
|
616
|
+
import_fs2.default.writeFileSync(outputPath, html);
|
|
617
|
+
return outputPath;
|
|
618
|
+
}
|
|
619
|
+
writeRawFile(filename, data) {
|
|
620
|
+
const outputPath = import_path2.default.join(process.cwd(), this.folderPath, filename);
|
|
621
|
+
import_fs2.default.mkdirSync(import_path2.default.dirname(outputPath), { recursive: true });
|
|
622
|
+
const content = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
623
|
+
import_fs2.default.writeFileSync(outputPath, content, "utf-8");
|
|
624
|
+
return outputPath;
|
|
625
|
+
}
|
|
626
|
+
copyTraceViewerAssets(skip) {
|
|
627
|
+
if (skip) return;
|
|
628
|
+
const traceViewerFolder = import_path2.default.join(
|
|
629
|
+
require.resolve("playwright-core"),
|
|
630
|
+
"..",
|
|
631
|
+
"lib",
|
|
632
|
+
"vite",
|
|
633
|
+
"traceViewer"
|
|
634
|
+
);
|
|
635
|
+
const traceViewerTargetFolder = import_path2.default.join(this.folderPath, "trace");
|
|
636
|
+
const traceViewerAssetsTargetFolder = import_path2.default.join(
|
|
637
|
+
traceViewerTargetFolder,
|
|
638
|
+
"assets"
|
|
639
|
+
);
|
|
640
|
+
import_fs2.default.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true });
|
|
641
|
+
for (const file of import_fs2.default.readdirSync(traceViewerFolder)) {
|
|
642
|
+
if (file.endsWith(".map") || file.includes("watch") || file.includes("assets"))
|
|
643
|
+
continue;
|
|
644
|
+
import_fs2.default.copyFileSync(
|
|
645
|
+
import_path2.default.join(traceViewerFolder, file),
|
|
646
|
+
import_path2.default.join(traceViewerTargetFolder, file)
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
const assetsFolder = import_path2.default.join(traceViewerFolder, "assets");
|
|
650
|
+
for (const file of import_fs2.default.readdirSync(assetsFolder)) {
|
|
651
|
+
if (file.endsWith(".map") || file.includes("xtermModule")) continue;
|
|
652
|
+
import_fs2.default.copyFileSync(
|
|
653
|
+
import_path2.default.join(assetsFolder, file),
|
|
654
|
+
import_path2.default.join(traceViewerAssetsTargetFolder, file)
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
// src/mergeData.ts
|
|
661
|
+
async function mergeAllData(options = {}) {
|
|
662
|
+
const folderPath = options.dir || "ortoni-report";
|
|
614
663
|
console.info(`Ortoni Report: Merging shard files in folder: ${folderPath}`);
|
|
615
|
-
|
|
616
|
-
|
|
664
|
+
if (!fs3.existsSync(folderPath)) {
|
|
665
|
+
console.error(`Ortoni Report: folder "${folderPath}" does not exist.`);
|
|
666
|
+
process.exitCode = 1;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
const filenames = fs3.readdirSync(folderPath).filter((f) => f.startsWith("ortoni-shard-") && f.endsWith(".json"));
|
|
670
|
+
if (filenames.length === 0) {
|
|
617
671
|
console.error("Ortoni Report: \u274C No shard files found to merge.");
|
|
618
|
-
process.
|
|
672
|
+
process.exitCode = 1;
|
|
673
|
+
return;
|
|
619
674
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
675
|
+
const shardFileIndex = (name) => {
|
|
676
|
+
const m = name.match(/ortoni-shard-(\d+)-of-(\d+)\.json$/);
|
|
677
|
+
return m ? parseInt(m[1], 10) : null;
|
|
678
|
+
};
|
|
679
|
+
const sortedFiles = filenames.map((f) => ({ f, idx: shardFileIndex(f) })).sort((a, b) => {
|
|
680
|
+
if (a.idx === null && b.idx === null) return a.f.localeCompare(b.f);
|
|
681
|
+
if (a.idx === null) return 1;
|
|
682
|
+
if (b.idx === null) return -1;
|
|
683
|
+
return a.idx - b.idx;
|
|
684
|
+
}).map((x) => x.f);
|
|
685
|
+
const dedupeByTestId = true;
|
|
686
|
+
const resultsById = /* @__PURE__ */ new Map();
|
|
687
|
+
const projectSet = /* @__PURE__ */ new Set();
|
|
688
|
+
let totalDurationSum = 0;
|
|
689
|
+
let totalDurationMax = 0;
|
|
623
690
|
let mergedUserConfig = null;
|
|
624
691
|
let mergedUserMeta = null;
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
692
|
+
const badShards = [];
|
|
693
|
+
for (const file of sortedFiles) {
|
|
694
|
+
const fullPath = path3.join(folderPath, file);
|
|
695
|
+
try {
|
|
696
|
+
const shardRaw = fs3.readFileSync(fullPath, "utf-8");
|
|
697
|
+
const shardData = JSON.parse(shardRaw);
|
|
698
|
+
if (!Array.isArray(shardData.results)) {
|
|
699
|
+
console.warn(
|
|
700
|
+
`Ortoni Report: Shard ${file} missing results array \u2014 skipping.`
|
|
701
|
+
);
|
|
702
|
+
badShards.push(file);
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
for (const r of shardData.results) {
|
|
706
|
+
const id = r.key;
|
|
707
|
+
if (dedupeByTestId) {
|
|
708
|
+
if (!resultsById.has(id)) {
|
|
709
|
+
resultsById.set(id, r);
|
|
710
|
+
} else {
|
|
711
|
+
console.info(
|
|
712
|
+
`Ortoni Report: Duplicate test ${id} found in ${file} \u2014 keeping first occurrence.`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
} else {
|
|
716
|
+
resultsById.set(`${id}::${Math.random().toString(36).slice(2)}`, r);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (Array.isArray(shardData.projectSet)) {
|
|
720
|
+
for (const p of shardData.projectSet) projectSet.add(p);
|
|
721
|
+
}
|
|
722
|
+
const dur = Number(shardData.duration) || 0;
|
|
723
|
+
totalDurationSum += dur;
|
|
724
|
+
if (dur > totalDurationMax) totalDurationMax = dur;
|
|
725
|
+
if (shardData.userConfig) {
|
|
726
|
+
if (!mergedUserConfig) mergedUserConfig = { ...shardData.userConfig };
|
|
727
|
+
else {
|
|
728
|
+
Object.keys(shardData.userConfig).forEach((k) => {
|
|
729
|
+
if (mergedUserConfig[k] === void 0 || mergedUserConfig[k] === null || mergedUserConfig[k] === "") {
|
|
730
|
+
mergedUserConfig[k] = shardData.userConfig[k];
|
|
731
|
+
} else if (shardData.userConfig[k] !== mergedUserConfig[k]) {
|
|
732
|
+
console.warn(
|
|
733
|
+
`Ortoni Report: userConfig mismatch for key "${k}" between shards. Using first value "${mergedUserConfig[k]}".`
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (shardData.userMeta) {
|
|
740
|
+
if (!mergedUserMeta) mergedUserMeta = { ...shardData.userMeta };
|
|
741
|
+
else {
|
|
742
|
+
mergedUserMeta.meta = {
|
|
743
|
+
...mergedUserMeta.meta || {},
|
|
744
|
+
...shardData.userMeta.meta || {}
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
} catch (err) {
|
|
749
|
+
console.error(`Ortoni Report: Failed to parse shard ${file}:`, err);
|
|
750
|
+
badShards.push(file);
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (badShards.length > 0) {
|
|
755
|
+
console.warn(
|
|
756
|
+
`Ortoni Report: Completed merge with ${badShards.length} bad shard(s) skipped:`,
|
|
757
|
+
badShards
|
|
628
758
|
);
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
759
|
+
}
|
|
760
|
+
const allResults = Array.from(resultsById.values());
|
|
761
|
+
const totalDuration = totalDurationSum;
|
|
762
|
+
const saveHistoryFromOptions = typeof options.saveHistory === "boolean" ? options.saveHistory : void 0;
|
|
763
|
+
const saveHistoryFromShard = mergedUserConfig && typeof mergedUserConfig.saveHistory === "boolean" ? mergedUserConfig.saveHistory : void 0;
|
|
764
|
+
const saveHistory = saveHistoryFromOptions ?? saveHistoryFromShard ?? true;
|
|
765
|
+
let dbManager;
|
|
766
|
+
let runId;
|
|
767
|
+
if (saveHistory) {
|
|
768
|
+
try {
|
|
769
|
+
dbManager = new DatabaseManager();
|
|
770
|
+
const dbPath = path3.join(folderPath, "ortoni-data-history.sqlite");
|
|
771
|
+
await dbManager.initialize(dbPath);
|
|
772
|
+
runId = await dbManager.saveTestRun();
|
|
773
|
+
if (typeof runId === "number") {
|
|
774
|
+
await dbManager.saveTestResults(runId, allResults);
|
|
775
|
+
console.info(
|
|
776
|
+
`Ortoni Report: Saved ${allResults.length} results to DB (runId=${runId}).`
|
|
777
|
+
);
|
|
778
|
+
} else {
|
|
779
|
+
console.warn(
|
|
780
|
+
"Ortoni Report: Failed to create test run in DB; proceeding without saving results."
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
} catch (err) {
|
|
784
|
+
console.error(
|
|
785
|
+
"Ortoni Report: Error while saving history to DB. Proceeding without DB:",
|
|
786
|
+
err
|
|
787
|
+
);
|
|
788
|
+
dbManager = void 0;
|
|
789
|
+
runId = void 0;
|
|
790
|
+
}
|
|
644
791
|
} else {
|
|
645
|
-
console.
|
|
792
|
+
console.info(
|
|
793
|
+
"Ortoni Report: Skipping history save (saveHistory=false). (Typical for CI runs)"
|
|
794
|
+
);
|
|
646
795
|
}
|
|
647
796
|
const htmlGenerator = new HTMLGenerator(
|
|
648
|
-
{ ...mergedUserConfig, meta: mergedUserMeta?.meta },
|
|
797
|
+
{ ...mergedUserConfig || {}, meta: mergedUserMeta?.meta },
|
|
649
798
|
dbManager
|
|
650
799
|
);
|
|
651
800
|
const finalReportData = await htmlGenerator.generateFinalReport(
|
|
@@ -656,15 +805,76 @@ async function mergeAllData(options) {
|
|
|
656
805
|
);
|
|
657
806
|
const fileManager = new FileManager(folderPath);
|
|
658
807
|
const outputFileName = options.file || "ortoni-report.html";
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
outputFilenamePath,
|
|
808
|
+
const outputPath = await fileManager.writeReportFile(
|
|
809
|
+
outputFileName,
|
|
662
810
|
finalReportData
|
|
663
811
|
);
|
|
664
812
|
console.log(`\u2705 Final merged report generated at ${outputPath}`);
|
|
813
|
+
if (runId) console.log(`\u2705 DB runId: ${runId}`);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// src/utils/expressServer.ts
|
|
817
|
+
var import_express = __toESM(require("express"));
|
|
818
|
+
var import_path3 = __toESM(require("path"));
|
|
819
|
+
var import_child_process = require("child_process");
|
|
820
|
+
function startReportServer(reportFolder, reportFilename, port = 2004, open2) {
|
|
821
|
+
const app = (0, import_express.default)();
|
|
822
|
+
app.use(import_express.default.static(reportFolder, { index: false }));
|
|
823
|
+
app.get("/", (_req, res) => {
|
|
824
|
+
try {
|
|
825
|
+
res.sendFile(import_path3.default.resolve(reportFolder, reportFilename));
|
|
826
|
+
} catch (error) {
|
|
827
|
+
console.error("Ortoni Report: Error sending report file:", error);
|
|
828
|
+
res.status(500).send("Error loading report");
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
try {
|
|
832
|
+
const server = app.listen(port, () => {
|
|
833
|
+
console.log(
|
|
834
|
+
`Server is running at http://localhost:${port}
|
|
835
|
+
Press Ctrl+C to stop.`
|
|
836
|
+
);
|
|
837
|
+
if (open2 === "always" || open2 === "on-failure") {
|
|
838
|
+
try {
|
|
839
|
+
openBrowser(`http://localhost:${port}`);
|
|
840
|
+
} catch (error) {
|
|
841
|
+
console.error("Ortoni Report: Error opening browser:", error);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
server.on("error", (error) => {
|
|
846
|
+
if (error.code === "EADDRINUSE") {
|
|
847
|
+
console.error(
|
|
848
|
+
`Ortoni Report: Port ${port} is already in use. Trying a different port...`
|
|
849
|
+
);
|
|
850
|
+
} else {
|
|
851
|
+
console.error("Ortoni Report: Server error:", error);
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
} catch (error) {
|
|
855
|
+
console.error("Ortoni Report: Error starting the server:", error);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function openBrowser(url) {
|
|
859
|
+
const platform = process.platform;
|
|
860
|
+
let command;
|
|
861
|
+
try {
|
|
862
|
+
if (platform === "win32") {
|
|
863
|
+
command = "cmd";
|
|
864
|
+
(0, import_child_process.spawn)(command, ["/c", "start", url]);
|
|
865
|
+
} else if (platform === "darwin") {
|
|
866
|
+
command = "open";
|
|
867
|
+
(0, import_child_process.spawn)(command, [url]);
|
|
868
|
+
} else {
|
|
869
|
+
command = "xdg-open";
|
|
870
|
+
(0, import_child_process.spawn)(command, [url]);
|
|
871
|
+
}
|
|
872
|
+
} catch (error) {
|
|
873
|
+
console.error("Ortoni Report: Error opening the browser:", error);
|
|
874
|
+
}
|
|
665
875
|
}
|
|
666
876
|
|
|
667
|
-
// src/cli
|
|
877
|
+
// src/cli.ts
|
|
668
878
|
import_commander.program.version("4.0.1").description("Ortoni Report - CLI");
|
|
669
879
|
import_commander.program.command("show-report").description("Open Ortoni Report").option(
|
|
670
880
|
"-d, --dir <path>",
|
|
@@ -676,22 +886,26 @@ import_commander.program.command("show-report").description("Open Ortoni Report"
|
|
|
676
886
|
"ortoni-report.html"
|
|
677
887
|
).option("-p, --port <port>", "Port to run the server", "2004").action((options) => {
|
|
678
888
|
const projectRoot = process.cwd();
|
|
679
|
-
const folderPath =
|
|
680
|
-
const filePath =
|
|
889
|
+
const folderPath = path5.resolve(projectRoot, options.dir);
|
|
890
|
+
const filePath = path5.resolve(folderPath, options.file);
|
|
681
891
|
const port = parseInt(options.port) || 2004;
|
|
682
|
-
if (!
|
|
892
|
+
if (!fs4.existsSync(filePath)) {
|
|
683
893
|
console.error(
|
|
684
894
|
`\u274C Error: The file "${filePath}" does not exist in "${folderPath}".`
|
|
685
895
|
);
|
|
686
896
|
process.exit(1);
|
|
687
897
|
}
|
|
688
|
-
startReportServer(folderPath,
|
|
898
|
+
startReportServer(folderPath, path5.basename(filePath), port, "always");
|
|
689
899
|
});
|
|
690
900
|
import_commander.program.command("merge-report").description("Merge sharded reports into one final report").option(
|
|
691
901
|
"-d, --dir <path>",
|
|
692
902
|
"Path to the folder containing shard files",
|
|
693
903
|
"ortoni-report"
|
|
694
904
|
).option("-f, --file <filename>", "Output report file", "ortoni-report.html").action(async (options) => {
|
|
695
|
-
await mergeAllData(
|
|
905
|
+
await mergeAllData({
|
|
906
|
+
dir: options.dir,
|
|
907
|
+
file: options.file,
|
|
908
|
+
saveHistory: false
|
|
909
|
+
});
|
|
696
910
|
});
|
|
697
911
|
import_commander.program.parse(process.argv);
|