ortoni-report 4.0.2-beta.1 → 4.1.0-test.0
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-INS3E7E6.mjs → chunk-4RZ5C7KY.mjs} +402 -296
- package/dist/{cli/cli.js → cli.js} +392 -190
- package/dist/cli.mjs +195 -0
- package/dist/index.html +2 -2
- package/dist/ortoni-report.d.mts +8 -1
- package/dist/ortoni-report.d.ts +8 -1
- package/dist/ortoni-report.js +204 -89
- package/dist/ortoni-report.mjs +18 -15
- 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-OZS6QIJS.mjs +0 -638
- package/dist/chunk-P57227VN.mjs +0 -633
- package/dist/chunk-QMTRYN5N.js +0 -635
- package/dist/chunk-TI33PMMQ.mjs +0 -639
- 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,75 +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
|
-
console.log("temp path - " + templatePath);
|
|
410
|
-
let html = import_fs.default.readFileSync(templatePath, "utf-8");
|
|
411
|
-
const reportJSON = JSON.stringify({
|
|
412
|
-
data
|
|
413
|
-
});
|
|
414
|
-
html = html.replace("__ORTONI_TEST_REPORTDATA__", reportJSON);
|
|
415
|
-
import_fs.default.writeFileSync(filename, html);
|
|
416
|
-
return filename;
|
|
417
|
-
}
|
|
418
|
-
writeRawFile(filename, data) {
|
|
419
|
-
const outputPath = import_path2.default.join(process.cwd(), this.folderPath, filename);
|
|
420
|
-
import_fs.default.mkdirSync(import_path2.default.dirname(outputPath), { recursive: true });
|
|
421
|
-
const content = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
422
|
-
import_fs.default.writeFileSync(outputPath, content, "utf-8");
|
|
423
|
-
return outputPath;
|
|
424
|
-
}
|
|
425
|
-
copyTraceViewerAssets(skip) {
|
|
426
|
-
if (skip) return;
|
|
427
|
-
const traceViewerFolder = import_path2.default.join(
|
|
428
|
-
require.resolve("playwright-core"),
|
|
429
|
-
"..",
|
|
430
|
-
"lib",
|
|
431
|
-
"vite",
|
|
432
|
-
"traceViewer"
|
|
433
|
-
);
|
|
434
|
-
const traceViewerTargetFolder = import_path2.default.join(this.folderPath, "trace");
|
|
435
|
-
const traceViewerAssetsTargetFolder = import_path2.default.join(
|
|
436
|
-
traceViewerTargetFolder,
|
|
437
|
-
"assets"
|
|
438
|
-
);
|
|
439
|
-
import_fs.default.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true });
|
|
440
|
-
for (const file of import_fs.default.readdirSync(traceViewerFolder)) {
|
|
441
|
-
if (file.endsWith(".map") || file.includes("watch") || file.includes("assets"))
|
|
442
|
-
continue;
|
|
443
|
-
import_fs.default.copyFileSync(
|
|
444
|
-
import_path2.default.join(traceViewerFolder, file),
|
|
445
|
-
import_path2.default.join(traceViewerTargetFolder, file)
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
const assetsFolder = import_path2.default.join(traceViewerFolder, "assets");
|
|
449
|
-
for (const file of import_fs.default.readdirSync(assetsFolder)) {
|
|
450
|
-
if (file.endsWith(".map") || file.includes("xtermModule")) continue;
|
|
451
|
-
import_fs.default.copyFileSync(
|
|
452
|
-
import_path2.default.join(assetsFolder, file),
|
|
453
|
-
import_path2.default.join(traceViewerAssetsTargetFolder, file)
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
|
|
459
326
|
// src/utils/groupProjects.ts
|
|
460
327
|
function groupResults(config, results) {
|
|
461
328
|
if (config.showProject) {
|
|
@@ -499,13 +366,46 @@ var HTMLGenerator = class {
|
|
|
499
366
|
);
|
|
500
367
|
return data;
|
|
501
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Return safe analytics/report data.
|
|
371
|
+
* If no dbManager is provided, return empty defaults and a note explaining why.
|
|
372
|
+
*/
|
|
502
373
|
async getReportData() {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
+
}
|
|
509
409
|
}
|
|
510
410
|
async prepareReportData(filteredResults, totalDuration, results, projectSet) {
|
|
511
411
|
const totalTests = filteredResults.length;
|
|
@@ -514,10 +414,10 @@ var HTMLGenerator = class {
|
|
|
514
414
|
const failed = filteredResults.filter(
|
|
515
415
|
(r) => r.status === "failed" || r.status === "timedOut"
|
|
516
416
|
).length;
|
|
517
|
-
const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
|
|
417
|
+
const successRate = totalTests === 0 ? "0.00" : ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
|
|
518
418
|
const allTags = /* @__PURE__ */ new Set();
|
|
519
419
|
results.forEach(
|
|
520
|
-
(result) => result.testTags.forEach((tag) => allTags.add(tag))
|
|
420
|
+
(result) => (result.testTags || []).forEach((tag) => allTags.add(tag))
|
|
521
421
|
);
|
|
522
422
|
const projectResults = this.calculateProjectResults(
|
|
523
423
|
filteredResults,
|
|
@@ -528,13 +428,31 @@ var HTMLGenerator = class {
|
|
|
528
428
|
const testHistories = await Promise.all(
|
|
529
429
|
results.map(async (result) => {
|
|
530
430
|
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
+
}
|
|
536
453
|
})
|
|
537
454
|
);
|
|
455
|
+
const reportData = await this.getReportData();
|
|
538
456
|
return {
|
|
539
457
|
summary: {
|
|
540
458
|
overAllResult: {
|
|
@@ -570,7 +488,7 @@ var HTMLGenerator = class {
|
|
|
570
488
|
showProject: this.ortoniConfig.showProject || false
|
|
571
489
|
},
|
|
572
490
|
analytics: {
|
|
573
|
-
reportData
|
|
491
|
+
reportData
|
|
574
492
|
}
|
|
575
493
|
};
|
|
576
494
|
}
|
|
@@ -608,48 +526,263 @@ var HTMLGenerator = class {
|
|
|
608
526
|
}
|
|
609
527
|
};
|
|
610
528
|
|
|
611
|
-
// src/
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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";
|
|
615
663
|
console.info(`Ortoni Report: Merging shard files in folder: ${folderPath}`);
|
|
616
|
-
|
|
617
|
-
|
|
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) {
|
|
618
671
|
console.error("Ortoni Report: \u274C No shard files found to merge.");
|
|
619
|
-
process.
|
|
672
|
+
process.exitCode = 1;
|
|
673
|
+
return;
|
|
620
674
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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 allResults = [];
|
|
686
|
+
const projectSet = /* @__PURE__ */ new Set();
|
|
687
|
+
let totalDurationSum = 0;
|
|
688
|
+
let totalDurationMax = 0;
|
|
624
689
|
let mergedUserConfig = null;
|
|
625
690
|
let mergedUserMeta = null;
|
|
626
|
-
for (const file of
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
691
|
+
for (const file of sortedFiles) {
|
|
692
|
+
const fullPath = path3.join(folderPath, file);
|
|
693
|
+
try {
|
|
694
|
+
const shardRaw = fs3.readFileSync(fullPath, "utf-8");
|
|
695
|
+
const shardData = JSON.parse(shardRaw);
|
|
696
|
+
if (!Array.isArray(shardData.results)) {
|
|
697
|
+
console.warn(
|
|
698
|
+
`Ortoni Report: Shard ${file} missing results array \u2014 skipping.`
|
|
699
|
+
);
|
|
700
|
+
badShards.push(file);
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
shardData.results.forEach((r) => allResults.push(r));
|
|
704
|
+
if (Array.isArray(shardData.projectSet)) {
|
|
705
|
+
shardData.projectSet.forEach((p) => projectSet.add(p));
|
|
706
|
+
}
|
|
707
|
+
const shardDur = Number(shardData.duration);
|
|
708
|
+
let durToAdd = 0;
|
|
709
|
+
if (!isNaN(shardDur) && shardDur > 0) {
|
|
710
|
+
durToAdd = shardDur;
|
|
711
|
+
} else {
|
|
712
|
+
const perTestSum = (Array.isArray(shardData.results) ? shardData.results : []).map((t) => Number(t.duration) || 0).reduce((a, b) => a + b, 0);
|
|
713
|
+
durToAdd = perTestSum;
|
|
714
|
+
}
|
|
715
|
+
totalDurationSum += durToAdd;
|
|
716
|
+
if (durToAdd > totalDurationMax) totalDurationMax = durToAdd;
|
|
717
|
+
if (shardData.userConfig) {
|
|
718
|
+
if (!mergedUserConfig) mergedUserConfig = { ...shardData.userConfig };
|
|
719
|
+
else {
|
|
720
|
+
Object.keys(shardData.userConfig).forEach((k) => {
|
|
721
|
+
if (mergedUserConfig[k] === void 0 || mergedUserConfig[k] === null || mergedUserConfig[k] === "") {
|
|
722
|
+
mergedUserConfig[k] = shardData.userConfig[k];
|
|
723
|
+
} else if (shardData.userConfig[k] !== mergedUserConfig[k]) {
|
|
724
|
+
console.warn(
|
|
725
|
+
`Ortoni Report: userConfig mismatch for key "${k}" between shards. Using first value "${mergedUserConfig[k]}".`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (shardData.userMeta) {
|
|
732
|
+
if (!mergedUserMeta) mergedUserMeta = { ...shardData.userMeta };
|
|
733
|
+
else {
|
|
734
|
+
mergedUserMeta.meta = {
|
|
735
|
+
...mergedUserMeta.meta || {},
|
|
736
|
+
...shardData.userMeta.meta || {}
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
} catch (err) {
|
|
741
|
+
console.error(`Ortoni Report: Failed to parse shard ${file}:`, err);
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
const totalDuration = totalDurationSum;
|
|
746
|
+
const saveHistoryFromOptions = typeof options.saveHistory === "boolean" ? options.saveHistory : void 0;
|
|
747
|
+
const saveHistoryFromShard = mergedUserConfig && typeof mergedUserConfig.saveHistory === "boolean" ? mergedUserConfig.saveHistory : void 0;
|
|
748
|
+
const saveHistory = saveHistoryFromOptions ?? saveHistoryFromShard ?? true;
|
|
749
|
+
let dbManager;
|
|
750
|
+
let runId;
|
|
751
|
+
if (saveHistory) {
|
|
752
|
+
try {
|
|
753
|
+
dbManager = new DatabaseManager();
|
|
754
|
+
const dbPath = path3.join(folderPath, "ortoni-data-history.sqlite");
|
|
755
|
+
await dbManager.initialize(dbPath);
|
|
756
|
+
runId = await dbManager.saveTestRun();
|
|
757
|
+
if (typeof runId === "number") {
|
|
758
|
+
await dbManager.saveTestResults(runId, allResults);
|
|
759
|
+
console.info(
|
|
760
|
+
`Ortoni Report: Saved ${allResults.length} results to DB (runId=${runId}).`
|
|
761
|
+
);
|
|
762
|
+
} else {
|
|
763
|
+
console.warn(
|
|
764
|
+
"Ortoni Report: Failed to create test run in DB; proceeding without saving results."
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
} catch (err) {
|
|
768
|
+
console.error(
|
|
769
|
+
"Ortoni Report: Error while saving history to DB. Proceeding without DB:",
|
|
770
|
+
err
|
|
771
|
+
);
|
|
772
|
+
dbManager = void 0;
|
|
773
|
+
runId = void 0;
|
|
774
|
+
}
|
|
645
775
|
} else {
|
|
646
|
-
console.
|
|
776
|
+
console.info(
|
|
777
|
+
"Ortoni Report: Skipping history save (saveHistory=false). (Typical for CI runs)"
|
|
778
|
+
);
|
|
647
779
|
}
|
|
648
780
|
const htmlGenerator = new HTMLGenerator(
|
|
649
|
-
{ ...mergedUserConfig, meta: mergedUserMeta?.meta },
|
|
781
|
+
{ ...mergedUserConfig || {}, meta: mergedUserMeta?.meta },
|
|
650
782
|
dbManager
|
|
651
783
|
);
|
|
652
784
|
const finalReportData = await htmlGenerator.generateFinalReport(
|
|
785
|
+
// filteredResults: keep same semantics (filter out skipped for display or pass as required)
|
|
653
786
|
allResults.filter((r) => r.status !== "skipped"),
|
|
654
787
|
totalDuration,
|
|
655
788
|
allResults,
|
|
@@ -657,15 +790,80 @@ async function mergerData(options) {
|
|
|
657
790
|
);
|
|
658
791
|
const fileManager = new FileManager(folderPath);
|
|
659
792
|
const outputFileName = options.file || "ortoni-report.html";
|
|
660
|
-
const outputFilenamePath = path3.join(folderPath, outputFileName);
|
|
661
793
|
const outputPath = fileManager.writeReportFile(
|
|
662
|
-
|
|
794
|
+
outputFileName,
|
|
663
795
|
finalReportData
|
|
664
796
|
);
|
|
665
797
|
console.log(`\u2705 Final merged report generated at ${outputPath}`);
|
|
798
|
+
console.log(
|
|
799
|
+
`\u2705 Shards merged: ${sortedFiles.length}, total tests merged: ${allResults.length}`
|
|
800
|
+
);
|
|
801
|
+
console.log(`\u2705 totalDuration (sum of shards) = ${totalDuration}`);
|
|
802
|
+
if (runId) console.log(`\u2705 DB runId: ${runId}`);
|
|
666
803
|
}
|
|
667
804
|
|
|
668
|
-
// src/
|
|
805
|
+
// src/utils/expressServer.ts
|
|
806
|
+
var import_express = __toESM(require("express"));
|
|
807
|
+
var import_path3 = __toESM(require("path"));
|
|
808
|
+
var import_child_process = require("child_process");
|
|
809
|
+
function startReportServer(reportFolder, reportFilename, port = 2004, open2) {
|
|
810
|
+
const app = (0, import_express.default)();
|
|
811
|
+
app.use(import_express.default.static(reportFolder, { index: false }));
|
|
812
|
+
app.get("/", (_req, res) => {
|
|
813
|
+
try {
|
|
814
|
+
res.sendFile(import_path3.default.resolve(reportFolder, reportFilename));
|
|
815
|
+
} catch (error) {
|
|
816
|
+
console.error("Ortoni Report: Error sending report file:", error);
|
|
817
|
+
res.status(500).send("Error loading report");
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
try {
|
|
821
|
+
const server = app.listen(port, () => {
|
|
822
|
+
console.log(
|
|
823
|
+
`Server is running at http://localhost:${port}
|
|
824
|
+
Press Ctrl+C to stop.`
|
|
825
|
+
);
|
|
826
|
+
if (open2 === "always" || open2 === "on-failure") {
|
|
827
|
+
try {
|
|
828
|
+
openBrowser(`http://localhost:${port}`);
|
|
829
|
+
} catch (error) {
|
|
830
|
+
console.error("Ortoni Report: Error opening browser:", error);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
server.on("error", (error) => {
|
|
835
|
+
if (error.code === "EADDRINUSE") {
|
|
836
|
+
console.error(
|
|
837
|
+
`Ortoni Report: Port ${port} is already in use. Trying a different port...`
|
|
838
|
+
);
|
|
839
|
+
} else {
|
|
840
|
+
console.error("Ortoni Report: Server error:", error);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
} catch (error) {
|
|
844
|
+
console.error("Ortoni Report: Error starting the server:", error);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
function openBrowser(url) {
|
|
848
|
+
const platform = process.platform;
|
|
849
|
+
let command;
|
|
850
|
+
try {
|
|
851
|
+
if (platform === "win32") {
|
|
852
|
+
command = "cmd";
|
|
853
|
+
(0, import_child_process.spawn)(command, ["/c", "start", url]);
|
|
854
|
+
} else if (platform === "darwin") {
|
|
855
|
+
command = "open";
|
|
856
|
+
(0, import_child_process.spawn)(command, [url]);
|
|
857
|
+
} else {
|
|
858
|
+
command = "xdg-open";
|
|
859
|
+
(0, import_child_process.spawn)(command, [url]);
|
|
860
|
+
}
|
|
861
|
+
} catch (error) {
|
|
862
|
+
console.error("Ortoni Report: Error opening the browser:", error);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// src/cli.ts
|
|
669
867
|
import_commander.program.version("4.0.1").description("Ortoni Report - CLI");
|
|
670
868
|
import_commander.program.command("show-report").description("Open Ortoni Report").option(
|
|
671
869
|
"-d, --dir <path>",
|
|
@@ -677,22 +875,26 @@ import_commander.program.command("show-report").description("Open Ortoni Report"
|
|
|
677
875
|
"ortoni-report.html"
|
|
678
876
|
).option("-p, --port <port>", "Port to run the server", "2004").action((options) => {
|
|
679
877
|
const projectRoot = process.cwd();
|
|
680
|
-
const folderPath =
|
|
681
|
-
const filePath =
|
|
878
|
+
const folderPath = path5.resolve(projectRoot, options.dir);
|
|
879
|
+
const filePath = path5.resolve(folderPath, options.file);
|
|
682
880
|
const port = parseInt(options.port) || 2004;
|
|
683
|
-
if (!
|
|
881
|
+
if (!fs4.existsSync(filePath)) {
|
|
684
882
|
console.error(
|
|
685
883
|
`\u274C Error: The file "${filePath}" does not exist in "${folderPath}".`
|
|
686
884
|
);
|
|
687
885
|
process.exit(1);
|
|
688
886
|
}
|
|
689
|
-
startReportServer(folderPath,
|
|
887
|
+
startReportServer(folderPath, path5.basename(filePath), port, "always");
|
|
690
888
|
});
|
|
691
889
|
import_commander.program.command("merge-report").description("Merge sharded reports into one final report").option(
|
|
692
890
|
"-d, --dir <path>",
|
|
693
891
|
"Path to the folder containing shard files",
|
|
694
892
|
"ortoni-report"
|
|
695
893
|
).option("-f, --file <filename>", "Output report file", "ortoni-report.html").action(async (options) => {
|
|
696
|
-
await
|
|
894
|
+
await mergeAllData({
|
|
895
|
+
dir: options.dir,
|
|
896
|
+
file: options.file,
|
|
897
|
+
saveHistory: false
|
|
898
|
+
});
|
|
697
899
|
});
|
|
698
900
|
import_commander.program.parse(process.argv);
|