@reportforge/playwright-pdf 0.3.1 → 0.4.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/README.md +3 -0
- package/dist/cli/export-feedback.js +87 -0
- package/dist/demo/cli.js +413 -59
- package/dist/index.d.mts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +444 -64
- package/dist/index.mjs +521 -141
- package/dist/templates/layouts/detailed.hbs +2 -0
- package/dist/templates/layouts/executive.hbs +2 -0
- package/dist/templates/partials/analysis-oneliner.hbs +3 -0
- package/dist/templates/partials/failure-analysis.hbs +42 -0
- package/dist/templates/styles/base.css +3 -0
- package/dist/templates/styles/detailed.css +21 -0
- package/package.json +8 -3
package/dist/demo/cli.js
CHANGED
|
@@ -1219,13 +1219,13 @@ var require_ast = __commonJS({
|
|
|
1219
1219
|
helperExpression: function helperExpression(node) {
|
|
1220
1220
|
return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
|
|
1221
1221
|
},
|
|
1222
|
-
scopedId: function scopedId(
|
|
1223
|
-
return /^\.|this\b/.test(
|
|
1222
|
+
scopedId: function scopedId(path11) {
|
|
1223
|
+
return /^\.|this\b/.test(path11.original);
|
|
1224
1224
|
},
|
|
1225
1225
|
// an ID is simple if it only has one part, and that part is not
|
|
1226
1226
|
// `..` or `this`.
|
|
1227
|
-
simpleId: function simpleId(
|
|
1228
|
-
return
|
|
1227
|
+
simpleId: function simpleId(path11) {
|
|
1228
|
+
return path11.parts.length === 1 && !AST.helpers.scopedId(path11) && !path11.depth;
|
|
1229
1229
|
}
|
|
1230
1230
|
}
|
|
1231
1231
|
};
|
|
@@ -2299,12 +2299,12 @@ var require_helpers2 = __commonJS({
|
|
|
2299
2299
|
loc
|
|
2300
2300
|
};
|
|
2301
2301
|
}
|
|
2302
|
-
function prepareMustache(
|
|
2302
|
+
function prepareMustache(path11, params, hash, open, strip, locInfo) {
|
|
2303
2303
|
var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
|
|
2304
2304
|
var decorator = /\*/.test(open);
|
|
2305
2305
|
return {
|
|
2306
2306
|
type: decorator ? "Decorator" : "MustacheStatement",
|
|
2307
|
-
path:
|
|
2307
|
+
path: path11,
|
|
2308
2308
|
params,
|
|
2309
2309
|
hash,
|
|
2310
2310
|
escaped,
|
|
@@ -2624,9 +2624,9 @@ var require_compiler = __commonJS({
|
|
|
2624
2624
|
},
|
|
2625
2625
|
DecoratorBlock: function DecoratorBlock(decorator) {
|
|
2626
2626
|
var program = decorator.program && this.compileProgram(decorator.program);
|
|
2627
|
-
var params = this.setupFullMustacheParams(decorator, program, void 0),
|
|
2627
|
+
var params = this.setupFullMustacheParams(decorator, program, void 0), path11 = decorator.path;
|
|
2628
2628
|
this.useDecorators = true;
|
|
2629
|
-
this.opcode("registerDecorator", params.length,
|
|
2629
|
+
this.opcode("registerDecorator", params.length, path11.original);
|
|
2630
2630
|
},
|
|
2631
2631
|
PartialStatement: function PartialStatement(partial) {
|
|
2632
2632
|
this.usePartial = true;
|
|
@@ -2690,46 +2690,46 @@ var require_compiler = __commonJS({
|
|
|
2690
2690
|
}
|
|
2691
2691
|
},
|
|
2692
2692
|
ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
|
|
2693
|
-
var
|
|
2694
|
-
this.opcode("getContext",
|
|
2693
|
+
var path11 = sexpr.path, name = path11.parts[0], isBlock = program != null || inverse != null;
|
|
2694
|
+
this.opcode("getContext", path11.depth);
|
|
2695
2695
|
this.opcode("pushProgram", program);
|
|
2696
2696
|
this.opcode("pushProgram", inverse);
|
|
2697
|
-
|
|
2698
|
-
this.accept(
|
|
2697
|
+
path11.strict = true;
|
|
2698
|
+
this.accept(path11);
|
|
2699
2699
|
this.opcode("invokeAmbiguous", name, isBlock);
|
|
2700
2700
|
},
|
|
2701
2701
|
simpleSexpr: function simpleSexpr(sexpr) {
|
|
2702
|
-
var
|
|
2703
|
-
|
|
2704
|
-
this.accept(
|
|
2702
|
+
var path11 = sexpr.path;
|
|
2703
|
+
path11.strict = true;
|
|
2704
|
+
this.accept(path11);
|
|
2705
2705
|
this.opcode("resolvePossibleLambda");
|
|
2706
2706
|
},
|
|
2707
2707
|
helperSexpr: function helperSexpr(sexpr, program, inverse) {
|
|
2708
|
-
var params = this.setupFullMustacheParams(sexpr, program, inverse),
|
|
2708
|
+
var params = this.setupFullMustacheParams(sexpr, program, inverse), path11 = sexpr.path, name = path11.parts[0];
|
|
2709
2709
|
if (this.options.knownHelpers[name]) {
|
|
2710
2710
|
this.opcode("invokeKnownHelper", params.length, name);
|
|
2711
2711
|
} else if (this.options.knownHelpersOnly) {
|
|
2712
2712
|
throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
|
|
2713
2713
|
} else {
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
this.accept(
|
|
2717
|
-
this.opcode("invokeHelper", params.length,
|
|
2714
|
+
path11.strict = true;
|
|
2715
|
+
path11.falsy = true;
|
|
2716
|
+
this.accept(path11);
|
|
2717
|
+
this.opcode("invokeHelper", params.length, path11.original, _ast2["default"].helpers.simpleId(path11));
|
|
2718
2718
|
}
|
|
2719
2719
|
},
|
|
2720
|
-
PathExpression: function PathExpression(
|
|
2721
|
-
this.addDepth(
|
|
2722
|
-
this.opcode("getContext",
|
|
2723
|
-
var name =
|
|
2720
|
+
PathExpression: function PathExpression(path11) {
|
|
2721
|
+
this.addDepth(path11.depth);
|
|
2722
|
+
this.opcode("getContext", path11.depth);
|
|
2723
|
+
var name = path11.parts[0], scoped = _ast2["default"].helpers.scopedId(path11), blockParamId = !path11.depth && !scoped && this.blockParamIndex(name);
|
|
2724
2724
|
if (blockParamId) {
|
|
2725
|
-
this.opcode("lookupBlockParam", blockParamId,
|
|
2725
|
+
this.opcode("lookupBlockParam", blockParamId, path11.parts);
|
|
2726
2726
|
} else if (!name) {
|
|
2727
2727
|
this.opcode("pushContext");
|
|
2728
|
-
} else if (
|
|
2728
|
+
} else if (path11.data) {
|
|
2729
2729
|
this.options.data = true;
|
|
2730
|
-
this.opcode("lookupData",
|
|
2730
|
+
this.opcode("lookupData", path11.depth, path11.parts, path11.strict);
|
|
2731
2731
|
} else {
|
|
2732
|
-
this.opcode("lookupOnContext",
|
|
2732
|
+
this.opcode("lookupOnContext", path11.parts, path11.falsy, path11.strict, scoped);
|
|
2733
2733
|
}
|
|
2734
2734
|
},
|
|
2735
2735
|
StringLiteral: function StringLiteral(string) {
|
|
@@ -3085,16 +3085,16 @@ var require_util = __commonJS({
|
|
|
3085
3085
|
}
|
|
3086
3086
|
exports2.urlGenerate = urlGenerate;
|
|
3087
3087
|
function normalize(aPath) {
|
|
3088
|
-
var
|
|
3088
|
+
var path11 = aPath;
|
|
3089
3089
|
var url = urlParse(aPath);
|
|
3090
3090
|
if (url) {
|
|
3091
3091
|
if (!url.path) {
|
|
3092
3092
|
return aPath;
|
|
3093
3093
|
}
|
|
3094
|
-
|
|
3094
|
+
path11 = url.path;
|
|
3095
3095
|
}
|
|
3096
|
-
var isAbsolute = exports2.isAbsolute(
|
|
3097
|
-
var parts =
|
|
3096
|
+
var isAbsolute = exports2.isAbsolute(path11);
|
|
3097
|
+
var parts = path11.split(/\/+/);
|
|
3098
3098
|
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
|
|
3099
3099
|
part = parts[i];
|
|
3100
3100
|
if (part === ".") {
|
|
@@ -3111,15 +3111,15 @@ var require_util = __commonJS({
|
|
|
3111
3111
|
}
|
|
3112
3112
|
}
|
|
3113
3113
|
}
|
|
3114
|
-
|
|
3115
|
-
if (
|
|
3116
|
-
|
|
3114
|
+
path11 = parts.join("/");
|
|
3115
|
+
if (path11 === "") {
|
|
3116
|
+
path11 = isAbsolute ? "/" : ".";
|
|
3117
3117
|
}
|
|
3118
3118
|
if (url) {
|
|
3119
|
-
url.path =
|
|
3119
|
+
url.path = path11;
|
|
3120
3120
|
return urlGenerate(url);
|
|
3121
3121
|
}
|
|
3122
|
-
return
|
|
3122
|
+
return path11;
|
|
3123
3123
|
}
|
|
3124
3124
|
exports2.normalize = normalize;
|
|
3125
3125
|
function join(aRoot, aPath) {
|
|
@@ -5922,8 +5922,8 @@ var require_printer = __commonJS({
|
|
|
5922
5922
|
return this.accept(sexpr.path) + " " + params + hash;
|
|
5923
5923
|
};
|
|
5924
5924
|
PrintVisitor.prototype.PathExpression = function(id) {
|
|
5925
|
-
var
|
|
5926
|
-
return (id.data ? "@" : "") + "PATH:" +
|
|
5925
|
+
var path11 = id.parts.join("/");
|
|
5926
|
+
return (id.data ? "@" : "") + "PATH:" + path11;
|
|
5927
5927
|
};
|
|
5928
5928
|
PrintVisitor.prototype.StringLiteral = function(string) {
|
|
5929
5929
|
return '"' + string.value + '"';
|
|
@@ -5964,8 +5964,8 @@ var require_lib = __commonJS({
|
|
|
5964
5964
|
handlebars.print = printer.print;
|
|
5965
5965
|
module2.exports = handlebars;
|
|
5966
5966
|
function extension(module3, filename) {
|
|
5967
|
-
var
|
|
5968
|
-
var templateString =
|
|
5967
|
+
var fs11 = require("fs");
|
|
5968
|
+
var templateString = fs11.readFileSync(filename, "utf8");
|
|
5969
5969
|
module3.exports = handlebars.compile(templateString);
|
|
5970
5970
|
}
|
|
5971
5971
|
if (typeof require !== "undefined" && require.extensions) {
|
|
@@ -5994,7 +5994,7 @@ var require_util2 = __commonJS({
|
|
|
5994
5994
|
"node_modules/chrome-finder/lib/util.js"(exports2, module2) {
|
|
5995
5995
|
"use strict";
|
|
5996
5996
|
init_cjs_shims();
|
|
5997
|
-
var
|
|
5997
|
+
var fs11 = require("fs");
|
|
5998
5998
|
var newLineRegex = /\r?\n/;
|
|
5999
5999
|
function sort(installations, priorities) {
|
|
6000
6000
|
const defaultPriority = 10;
|
|
@@ -6012,7 +6012,7 @@ var require_util2 = __commonJS({
|
|
|
6012
6012
|
return false;
|
|
6013
6013
|
}
|
|
6014
6014
|
try {
|
|
6015
|
-
|
|
6015
|
+
fs11.accessSync(file);
|
|
6016
6016
|
return true;
|
|
6017
6017
|
} catch (e) {
|
|
6018
6018
|
return false;
|
|
@@ -6023,7 +6023,7 @@ var require_util2 = __commonJS({
|
|
|
6023
6023
|
return false;
|
|
6024
6024
|
}
|
|
6025
6025
|
try {
|
|
6026
|
-
var stat =
|
|
6026
|
+
var stat = fs11.statSync(file);
|
|
6027
6027
|
return stat && typeof stat.isFile === "function" && stat.isFile();
|
|
6028
6028
|
} catch (e) {
|
|
6029
6029
|
return false;
|
|
@@ -6043,7 +6043,7 @@ var require_win32 = __commonJS({
|
|
|
6043
6043
|
"node_modules/chrome-finder/lib/win32.js"(exports2, module2) {
|
|
6044
6044
|
"use strict";
|
|
6045
6045
|
init_cjs_shims();
|
|
6046
|
-
var
|
|
6046
|
+
var path11 = require("path");
|
|
6047
6047
|
var { canAccess } = require_util2();
|
|
6048
6048
|
function win32() {
|
|
6049
6049
|
const installations = [];
|
|
@@ -6057,7 +6057,7 @@ var require_win32 = __commonJS({
|
|
|
6057
6057
|
const prefixes = [process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"]];
|
|
6058
6058
|
prefixes.forEach((prefix) => suffixes.forEach((suffix) => {
|
|
6059
6059
|
if (prefix) {
|
|
6060
|
-
const chromePath =
|
|
6060
|
+
const chromePath = path11.join(prefix, suffix);
|
|
6061
6061
|
if (canAccess(chromePath)) {
|
|
6062
6062
|
installations.push(chromePath);
|
|
6063
6063
|
}
|
|
@@ -6075,7 +6075,7 @@ var require_darwin = __commonJS({
|
|
|
6075
6075
|
"use strict";
|
|
6076
6076
|
init_cjs_shims();
|
|
6077
6077
|
var { execSync: execSync2 } = require("child_process");
|
|
6078
|
-
var
|
|
6078
|
+
var path11 = require("path");
|
|
6079
6079
|
var { canAccess, newLineRegex, sort } = require_util2();
|
|
6080
6080
|
function darwin() {
|
|
6081
6081
|
const suffixes = ["/Contents/MacOS/Google Chrome Canary", "/Contents/MacOS/Google Chrome", "/Contents/MacOS/Chromium"];
|
|
@@ -6085,7 +6085,7 @@ var require_darwin = __commonJS({
|
|
|
6085
6085
|
${LSREGISTER} -dump | grep -E -i -o '/.+(google chrome( canary)?|chromium)\\.app(\\s|$)' | grep -E -v 'Caches|TimeMachine|Temporary|/Volumes|\\.Trash'
|
|
6086
6086
|
`).toString().split(newLineRegex).forEach((inst) => {
|
|
6087
6087
|
suffixes.forEach((suffix) => {
|
|
6088
|
-
const execPath =
|
|
6088
|
+
const execPath = path11.join(inst.trim(), suffix);
|
|
6089
6089
|
if (canAccess(execPath)) {
|
|
6090
6090
|
installations.push(execPath);
|
|
6091
6091
|
}
|
|
@@ -6114,8 +6114,8 @@ var require_linux = __commonJS({
|
|
|
6114
6114
|
"use strict";
|
|
6115
6115
|
init_cjs_shims();
|
|
6116
6116
|
var { execSync: execSync2, execFileSync: execFileSync2 } = require("child_process");
|
|
6117
|
-
var
|
|
6118
|
-
var
|
|
6117
|
+
var path11 = require("path");
|
|
6118
|
+
var fs11 = require("fs");
|
|
6119
6119
|
var { canAccess, sort, isExecutable, newLineRegex } = require_util2();
|
|
6120
6120
|
function findChromeExecutablesForLinuxDesktop(folder) {
|
|
6121
6121
|
const argumentsRegex = /(^[^ ]+).*/;
|
|
@@ -6132,7 +6132,7 @@ var require_linux = __commonJS({
|
|
|
6132
6132
|
function linux() {
|
|
6133
6133
|
let installations = [];
|
|
6134
6134
|
const desktopInstallationFolders = [
|
|
6135
|
-
|
|
6135
|
+
path11.join(require("os").homedir(), ".local/share/applications/"),
|
|
6136
6136
|
"/usr/share/applications/"
|
|
6137
6137
|
];
|
|
6138
6138
|
desktopInstallationFolders.forEach((folder) => {
|
|
@@ -6158,7 +6158,7 @@ var require_linux = __commonJS({
|
|
|
6158
6158
|
].map((possiblePath) => {
|
|
6159
6159
|
try {
|
|
6160
6160
|
const chromePathToTest = possiblePath + "/" + executable;
|
|
6161
|
-
if (
|
|
6161
|
+
if (fs11.existsSync(chromePathToTest) && canAccess(chromePathToTest) && isExecutable(chromePathToTest)) {
|
|
6162
6162
|
installations.push(chromePathToTest);
|
|
6163
6163
|
return chromePathToTest;
|
|
6164
6164
|
}
|
|
@@ -6494,6 +6494,42 @@ function formatDuration(ms) {
|
|
|
6494
6494
|
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
6495
6495
|
}
|
|
6496
6496
|
|
|
6497
|
+
// src/analysis/SummaryWriter.ts
|
|
6498
|
+
init_cjs_shims();
|
|
6499
|
+
var CATEGORY_LABEL = {
|
|
6500
|
+
assertion: "Assertion failure",
|
|
6501
|
+
timeout: "Timeout",
|
|
6502
|
+
"locator-not-found": "Element not found",
|
|
6503
|
+
network: "Network error",
|
|
6504
|
+
navigation: "Navigation error",
|
|
6505
|
+
"env-config": "Environment / config",
|
|
6506
|
+
"flaky-by-retry": "Flaky (passed on retry)",
|
|
6507
|
+
unknown: "Unclassified"
|
|
6508
|
+
};
|
|
6509
|
+
var SHORT_LABEL = {
|
|
6510
|
+
assertion: "assertion",
|
|
6511
|
+
timeout: "timeout",
|
|
6512
|
+
"locator-not-found": "element not found",
|
|
6513
|
+
network: "network",
|
|
6514
|
+
navigation: "navigation",
|
|
6515
|
+
"env-config": "env/config",
|
|
6516
|
+
"flaky-by-retry": "flaky",
|
|
6517
|
+
unknown: "unclassified"
|
|
6518
|
+
};
|
|
6519
|
+
function buildOneLiner(clusters) {
|
|
6520
|
+
if (clusters.length === 0) return "";
|
|
6521
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
6522
|
+
for (const c of clusters) {
|
|
6523
|
+
byCategory.set(c.category, (byCategory.get(c.category) ?? 0) + c.count);
|
|
6524
|
+
}
|
|
6525
|
+
const head = `${clusters.length} root ${clusters.length === 1 ? "cause" : "causes"}`;
|
|
6526
|
+
const tally = [...byCategory.entries()].sort((a, b) => b[1] - a[1]).map(([cat, count]) => `${count} ${SHORT_LABEL[cat]}`);
|
|
6527
|
+
return [head, ...tally].join(" \xB7 ");
|
|
6528
|
+
}
|
|
6529
|
+
function dominantCategory(clusters) {
|
|
6530
|
+
return clusters[0]?.category ?? "unknown";
|
|
6531
|
+
}
|
|
6532
|
+
|
|
6497
6533
|
// src/templates/engine.ts
|
|
6498
6534
|
var TemplateEngine = class {
|
|
6499
6535
|
constructor() {
|
|
@@ -6720,6 +6756,10 @@ var TemplateEngine = class {
|
|
|
6720
6756
|
);
|
|
6721
6757
|
this.handlebars.registerHelper("lt", (a, b) => a < b);
|
|
6722
6758
|
this.handlebars.registerHelper("or", (a, b) => Boolean(a) || Boolean(b));
|
|
6759
|
+
this.handlebars.registerHelper("categoryLabel", (cat) => CATEGORY_LABEL[cat] ?? cat);
|
|
6760
|
+
this.handlebars.registerHelper("strengthLabel", (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : "");
|
|
6761
|
+
this.handlebars.registerHelper("pluralise", (n, one, many) => n === 1 ? one : many);
|
|
6762
|
+
this.handlebars.registerHelper("subtract", (a, b) => (a ?? 0) - (b ?? 0));
|
|
6723
6763
|
this.handlebars.registerHelper("statusBadge", (status) => {
|
|
6724
6764
|
const map = {
|
|
6725
6765
|
passed: "badge--passed",
|
|
@@ -7133,6 +7173,7 @@ init_cjs_shims();
|
|
|
7133
7173
|
|
|
7134
7174
|
// src/license/pubkey-bundle.ts
|
|
7135
7175
|
init_cjs_shims();
|
|
7176
|
+
var LICENSE_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAbvknstk0gxoJ7UwATdlxR8btxvO7u7RdpO1uV90PtuE=\n-----END PUBLIC KEY-----";
|
|
7136
7177
|
|
|
7137
7178
|
// src/pdf/PdfGenerator.ts
|
|
7138
7179
|
var PdfGenerator = class {
|
|
@@ -7181,11 +7222,11 @@ var PdfGenerator = class {
|
|
|
7181
7222
|
if (!licenseInfo.jwt) {
|
|
7182
7223
|
throw new Error("[reportforge] License JWT missing \u2014 PDF generation skipped.");
|
|
7183
7224
|
}
|
|
7184
|
-
if (!
|
|
7225
|
+
if (!LICENSE_PUBLIC_KEY_PEM2) {
|
|
7185
7226
|
throw new Error("[reportforge] LICENSE_PUBLIC_KEY_PEM missing \u2014 cannot verify license JWT.");
|
|
7186
7227
|
}
|
|
7187
7228
|
try {
|
|
7188
|
-
const payload = new JwtVerifier(
|
|
7229
|
+
const payload = new JwtVerifier(LICENSE_PUBLIC_KEY_PEM2).verify(licenseInfo.jwt);
|
|
7189
7230
|
if (payload.exp * 1e3 <= Date.now()) {
|
|
7190
7231
|
throw new Error("JWT expired");
|
|
7191
7232
|
}
|
|
@@ -7843,8 +7884,8 @@ function getErrorMap() {
|
|
|
7843
7884
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
7844
7885
|
init_cjs_shims();
|
|
7845
7886
|
var makeIssue = (params) => {
|
|
7846
|
-
const { data, path:
|
|
7847
|
-
const fullPath = [...
|
|
7887
|
+
const { data, path: path11, errorMaps, issueData } = params;
|
|
7888
|
+
const fullPath = [...path11, ...issueData.path || []];
|
|
7848
7889
|
const fullIssue = {
|
|
7849
7890
|
...issueData,
|
|
7850
7891
|
path: fullPath
|
|
@@ -7964,11 +8005,11 @@ var errorUtil;
|
|
|
7964
8005
|
|
|
7965
8006
|
// node_modules/zod/v3/types.js
|
|
7966
8007
|
var ParseInputLazyPath = class {
|
|
7967
|
-
constructor(parent, value,
|
|
8008
|
+
constructor(parent, value, path11, key) {
|
|
7968
8009
|
this._cachedPath = [];
|
|
7969
8010
|
this.parent = parent;
|
|
7970
8011
|
this.data = value;
|
|
7971
|
-
this._path =
|
|
8012
|
+
this._path = path11;
|
|
7972
8013
|
this._key = key;
|
|
7973
8014
|
}
|
|
7974
8015
|
get path() {
|
|
@@ -11448,6 +11489,16 @@ var notifyConfigSchema = external_exports.object({
|
|
|
11448
11489
|
discord: channelConfigSchema.optional(),
|
|
11449
11490
|
email: emailChannelConfigSchema.optional()
|
|
11450
11491
|
}).optional();
|
|
11492
|
+
var failureAnalysisConfigSchema = external_exports.object({
|
|
11493
|
+
enabled: external_exports.boolean().default(true),
|
|
11494
|
+
maxClusters: external_exports.number().int().min(1).max(50).default(10),
|
|
11495
|
+
// Qualitative floor (not a numeric % — consistent with the match-strength design).
|
|
11496
|
+
minStrength: external_exports.enum(["weak", "moderate", "strong"]).default("weak"),
|
|
11497
|
+
maxFailuresToAnalyse: external_exports.number().int().min(1).default(500),
|
|
11498
|
+
// Consumed by the Plan 3 feedback collector; inert until then.
|
|
11499
|
+
collectUnclassified: external_exports.boolean().default(true),
|
|
11500
|
+
autoUpdateModel: external_exports.boolean().default(true)
|
|
11501
|
+
}).default({});
|
|
11451
11502
|
var ReporterOptionsSchema = external_exports.object({
|
|
11452
11503
|
outputFile: external_exports.string().optional().default(DEFAULT_OPTIONS.outputFile),
|
|
11453
11504
|
template: external_exports.union([
|
|
@@ -11477,6 +11528,7 @@ var ReporterOptionsSchema = external_exports.object({
|
|
|
11477
11528
|
historySize: external_exports.number().int().min(2).optional().default(DEFAULT_OPTIONS.historySize),
|
|
11478
11529
|
showTrend: external_exports.boolean().optional().default(DEFAULT_OPTIONS.showTrend),
|
|
11479
11530
|
flakinessTopN: external_exports.number().int().min(0, "flakinessTopN must be 0 or greater").optional().default(DEFAULT_OPTIONS.flakinessTopN),
|
|
11531
|
+
failureAnalysis: failureAnalysisConfigSchema,
|
|
11480
11532
|
templatePath: external_exports.union([
|
|
11481
11533
|
external_exports.string().regex(/\.hbs$/, "templatePath must end with .hbs").refine((v) => v.slice(0, -4).trim().length > 0, "templatePath basename must not be empty"),
|
|
11482
11534
|
external_exports.array(
|
|
@@ -11500,6 +11552,307 @@ ${issues}`);
|
|
|
11500
11552
|
init_cjs_shims();
|
|
11501
11553
|
var TEMPLATE_IDS = ["minimal", "detailed", "executive"];
|
|
11502
11554
|
|
|
11555
|
+
// src/analysis/index.ts
|
|
11556
|
+
init_cjs_shims();
|
|
11557
|
+
|
|
11558
|
+
// src/analysis/ModelStore.ts
|
|
11559
|
+
init_cjs_shims();
|
|
11560
|
+
var import_node_fs = __toESM(require("fs"));
|
|
11561
|
+
var import_node_os = __toESM(require("os"));
|
|
11562
|
+
var import_node_path = __toESM(require("path"));
|
|
11563
|
+
var import_node_crypto = require("crypto");
|
|
11564
|
+
|
|
11565
|
+
// src/analysis/model-bundle.ts
|
|
11566
|
+
init_cjs_shims();
|
|
11567
|
+
var MODEL = { "alpha": 1, "labels": ["assertion", "env-config", "flaky-by-retry", "locator-not-found", "navigation", "network", "timeout"], "logLikelihoods": { "assertion": { "NUM": -4.574710978503383, "assertionerror": -4.980176086611547, "be": -4.980176086611547, "cart": -4.980176086611547, "checkout": -4.980176086611547, "com": -4.980176086611547, "dashboard": -4.980176086611547, "deep": -4.980176086611547, "equal": -4.980176086611547, "equality": -4.980176086611547, "error": -4.980176086611547, "expect": -3.5938817254916566, "expected": -3.1083739097099556, "failed": -4.980176086611547, "false": -4.980176086611547, "got": -4.980176086611547, "id": -4.574710978503383, "john": -4.980176086611547, "locator": -4.574710978503383, "login": -4.980176086611547, "must": -4.980176086611547, "page": -4.980176086611547, "pattern": -4.980176086611547, "received": -3.275427994373122, "string": -4.980176086611547, "to": -4.980176086611547, "tobe": -4.980176086611547, "tobetruthy": -4.980176086611547, "toequal": -4.980176086611547, "tohavecount": -4.980176086611547, "tohavetext": -4.980176086611547, "tohavetitle": -4.980176086611547, "tohavevalue": -4.980176086611547, "truthy": -4.980176086611547, "value": -4.980176086611547 }, "env-config": { "NUM": -4.997212273764115, "api": -4.997212273764115, "at": -4.997212273764115, "back": -4.997212273764115, "base": -4.997212273764115, "baseurl": -4.997212273764115, "browsers": -4.997212273764115, "cannot": -4.997212273764115, "ci": -4.997212273764115, "config": -4.304065093204169, "directory": -4.591747165655951, "dotenv": -4.997212273764115, "enoent": -4.997212273764115, "env": -4.591747165655951, "environment": -4.591747165655951, "error": -4.08092154188996, "existent": -4.997212273764115, "failed": -4.997212273764115, "falling": -4.997212273764115, "file": -4.997212273764115, "find": -4.997212273764115, "from": -4.997212273764115, "in": -4.591747165655951, "invalid": -4.997212273764115, "is": -4.997212273764115, "json": -4.591747165655951, "key": -4.997212273764115, "load": -4.997212273764115, "missing": -4.997212273764115, "module": -4.997212273764115, "no": -4.997212273764115, "non": -4.997212273764115, "not": -4.997212273764115, "open": -4.997212273764115, "or": -4.997212273764115, "path": -4.997212273764115, "playwright": -4.591747165655951, "points": -4.997212273764115, "position": -4.997212273764115, "required": -4.997212273764115, "set": -4.997212273764115, "spec": -4.997212273764115, "such": -4.997212273764115, "syntaxerror": -4.997212273764115, "test": -4.997212273764115, "to": -4.304065093204169, "token": -4.997212273764115, "ts": -4.997212273764115, "tsconfig": -4.997212273764115, "undefined": -4.591747165655951, "unexpected": -4.997212273764115, "url": -4.997212273764115, "values": -4.997212273764115, "variable": -4.591747165655951 }, "flaky-by-retry": { "after": -4.58836306767171, "and": -4.993828175779875, "animation": -4.993828175779875, "attempt": -4.07753744390572, "between": -4.993828175779875, "blip": -4.993828175779875, "cause": -4.993828175779875, "change": -4.993828175779875, "click": -4.993828175779875, "code": -4.993828175779875, "commit": -4.993828175779875, "condition": -4.993828175779875, "dependency": -4.993828175779875, "detached": -4.993828175779875, "deterministic": -4.993828175779875, "element": -4.993828175779875, "failed": -4.300680995219929, "fails": -4.993828175779875, "flaky": -4.07753744390572, "green": -4.993828175779875, "in": -4.993828175779875, "intermittent": -4.993828175779875, "locator": -4.993828175779875, "marked": -4.993828175779875, "network": -4.993828175779875, "no": -4.993828175779875, "non": -4.993828175779875, "on": -3.895215887111765, "order": -4.993828175779875, "passed": -3.895215887111765, "quarantined": -4.993828175779875, "race": -4.993828175779875, "reproducible": -4.993828175779875, "rerun": -4.993828175779875, "retry": -4.07753744390572, "runs": -4.993828175779875, "same": -4.993828175779875, "second": -4.993828175779875, "stale": -4.993828175779875, "succeeded": -4.993828175779875, "test": -4.58836306767171, "the": -4.993828175779875, "then": -4.993828175779875, "timeout": -4.993828175779875, "unstable": -4.993828175779875, "with": -4.993828175779875, "without": -4.993828175779875 }, "locator-not-found": { "an": -4.993828175779875, "attached": -4.993828175779875, "btn": -4.993828175779875, "button": -4.58836306767171, "cart": -4.993828175779875, "checkout": -4.993828175779875, "click": -4.993828175779875, "data": -4.993828175779875, "div": -4.993828175779875, "dom": -4.993828175779875, "element": -4.07753744390572, "elements": -4.300680995219929, "error": -4.58836306767171, "find": -4.993828175779875, "for": -4.58836306767171, "found": -4.58836306767171, "getbyrole": -4.993828175779875, "getbytext": -4.993828175779875, "icon": -4.993828175779875, "id": -4.993828175779875, "is": -4.993828175779875, "locator": -4.07753744390572, "matched": -4.993828175779875, "matches": -4.993828175779875, "missing": -4.993828175779875, "mode": -4.58836306767171, "name": -4.993828175779875, "no": -4.58836306767171, "node": -4.993828175779875, "not": -4.58836306767171, "resolved": -4.58836306767171, "save": -4.993828175779875, "selector": -4.58836306767171, "strict": -4.58836306767171, "submit": -4.993828175779875, "testid": -4.993828175779875, "textcontent": -4.993828175779875, "the": -4.58836306767171, "to": -4.07753744390572, "total": -4.993828175779875, "unable": -4.993828175779875, "violation": -4.58836306767171, "waiting": -4.993828175779875, "with": -4.993828175779875, "xpath": -4.993828175779875 }, "navigation": { "NUM": -4.983606621708336, "aborted": -4.983606621708336, "another": -4.983606621708336, "back": -4.983606621708336, "because": -4.578141513600172, "by": -4.578141513600172, "checkout": -4.983606621708336, "context": -4.983606621708336, "crashed": -4.983606621708336, "dashboard": -4.983606621708336, "destroyed": -4.983606621708336, "detached": -4.983606621708336, "entry": -4.983606621708336, "err": -4.578141513600172, "exceeded": -4.983606621708336, "execution": -4.983606621708336, "failed": -4.983606621708336, "frame": -4.578141513600172, "goback": -4.983606621708336, "goto": -4.290459441148391, "history": -4.983606621708336, "home": -4.983606621708336, "interrupted": -4.578141513600172, "is": -4.983606621708336, "likely": -4.983606621708336, "login": -4.983606621708336, "many": -4.983606621708336, "maybe": -4.983606621708336, "most": -4.983606621708336, "navigate": -4.983606621708336, "navigating": -4.983606621708336, "navigation": -3.7308436532129683, "net": -4.578141513600172, "no": -4.983606621708336, "of": -4.983606621708336, "page": -3.884994333040227, "redirects": -4.983606621708336, "timeout": -4.983606621708336, "to": -3.7308436532129683, "too": -4.983606621708336, "waitforurl": -4.983606621708336, "was": -4.578141513600172 }, "network": { "NUM": -4.57126863431241, "URL": -4.283586561860629, "again": -4.976733742420574, "api": -4.976733742420574, "apirequestcontext": -4.976733742420574, "at": -4.976733742420574, "body": -4.976733742420574, "connect": -4.976733742420574, "connection": -4.57126863431241, "disconnected": -4.976733742420574, "eai": -4.976733742420574, "econnrefused": -4.976733742420574, "err": -3.8781214537524646, "error": -4.976733742420574, "failed": -4.283586561860629, "fetch": -4.976733742420574, "fetching": -4.976733742420574, "from": -4.976733742420574, "get": -4.976733742420574, "getaddrinfo": -4.976733742420574, "goto": -4.976733742420574, "hang": -4.976733742420574, "internet": -4.976733742420574, "loading": -4.976733742420574, "name": -4.976733742420574, "net": -3.8781214537524646, "not": -4.976733742420574, "on": -4.976733742420574, "out": -4.976733742420574, "page": -4.57126863431241, "products": -4.976733742420574, "protocol": -4.976733742420574, "reading": -4.976733742420574, "reason": -4.976733742420574, "refused": -4.976733742420574, "request": -4.57126863431241, "resolved": -4.976733742420574, "resource": -4.976733742420574, "response": -4.976733742420574, "route": -4.976733742420574, "socket": -4.976733742420574, "ssl": -4.976733742420574, "timed": -4.976733742420574, "to": -4.976733742420574, "up": -4.976733742420574, "upstream": -4.976733742420574, "while": -4.976733742420574 }, "timeout": { "NUM": -3.4965075614664802, "after": -5.000584958242754, "be": -4.59511985013459, "cart": -5.000584958242754, "click": -5.000584958242754, "element": -5.000584958242754, "event": -5.000584958242754, "exceeded": -3.6142905971228636, "expect": -5.000584958242754, "fill": -5.000584958242754, "for": -3.7478219897473863, "function": -5.000584958242754, "load": -5.000584958242754, "locator": -4.084294226368599, "navigation": -5.000584958242754, "of": -5.000584958242754, "out": -5.000584958242754, "page": -4.59511985013459, "running": -5.000584958242754, "selector": -5.000584958242754, "submit": -5.000584958242754, "test": -4.59511985013459, "timed": -5.000584958242754, "timeout": -3.6142905971228636, "timeouterror": -4.59511985013459, "to": -4.59511985013459, "tobevisible": -5.000584958242754, "visible": -4.59511985013459, "waitforloadstate": -5.000584958242754, "waitforselector": -5.000584958242754, "waiting": -3.7478219897473863, "while": -5.000584958242754 } }, "playwrightVersion": "1.59.1", "priors": { "assertion": -1.9636097261547143, "env-config": -1.9636097261547143, "flaky-by-retry": -1.9636097261547143, "locator-not-found": -1.9636097261547143, "navigation": -1.9636097261547143, "network": -1.9636097261547143, "timeout": -1.845826690498331 }, "totals": { "assertion": 64, "env-config": 69, "flaky-by-retry": 68, "locator-not-found": 68, "navigation": 65, "network": 63, "timeout": 70 }, "version": 1, "vocabSize": 227 };
|
|
11568
|
+
|
|
11569
|
+
// src/analysis/ModelStore.ts
|
|
11570
|
+
var MODEL_SIG_DOMAIN = "reportforge-model-sig:v1\n";
|
|
11571
|
+
var testDeps = null;
|
|
11572
|
+
function cacheFile() {
|
|
11573
|
+
return testDeps?.cacheFile ?? import_node_path.default.join(import_node_os.default.homedir(), ".reportforge", "model.json");
|
|
11574
|
+
}
|
|
11575
|
+
function publicKey() {
|
|
11576
|
+
const pem = testDeps?.publicKeyPem ?? LICENSE_PUBLIC_KEY_PEM;
|
|
11577
|
+
try {
|
|
11578
|
+
return pem && pem.includes("PUBLIC KEY") ? (0, import_node_crypto.createPublicKey)(pem) : null;
|
|
11579
|
+
} catch {
|
|
11580
|
+
return null;
|
|
11581
|
+
}
|
|
11582
|
+
}
|
|
11583
|
+
function verifyModelSignature(modelJson, signatureB64url) {
|
|
11584
|
+
const key = publicKey();
|
|
11585
|
+
if (!key) return false;
|
|
11586
|
+
try {
|
|
11587
|
+
return (0, import_node_crypto.verify)(null, Buffer.from(MODEL_SIG_DOMAIN + modelJson), key, Buffer.from(signatureB64url, "base64url"));
|
|
11588
|
+
} catch {
|
|
11589
|
+
return false;
|
|
11590
|
+
}
|
|
11591
|
+
}
|
|
11592
|
+
function schemaValid(m) {
|
|
11593
|
+
const x = m;
|
|
11594
|
+
return !!x && Array.isArray(x.labels) && x.labels.length > 0 && typeof x.version === "number" && !!x.priors && !!x.totals && !!x.logLikelihoods && typeof x.vocabSize === "number" && typeof x.alpha === "number";
|
|
11595
|
+
}
|
|
11596
|
+
function readCache() {
|
|
11597
|
+
try {
|
|
11598
|
+
const raw = import_node_fs.default.readFileSync(cacheFile(), "utf8");
|
|
11599
|
+
const c = JSON.parse(raw);
|
|
11600
|
+
if (typeof c.version === "number" && typeof c.modelJson === "string" && typeof c.signature === "string") {
|
|
11601
|
+
return c;
|
|
11602
|
+
}
|
|
11603
|
+
} catch {
|
|
11604
|
+
}
|
|
11605
|
+
return null;
|
|
11606
|
+
}
|
|
11607
|
+
function loadModel(autoUpdate) {
|
|
11608
|
+
if (!autoUpdate) return MODEL;
|
|
11609
|
+
const c = readCache();
|
|
11610
|
+
if (!c || c.version <= MODEL.version) return MODEL;
|
|
11611
|
+
if (!verifyModelSignature(c.modelJson, c.signature)) {
|
|
11612
|
+
logger.debug(`cached model v${c.version} signature invalid \u2014 using bundled (possible tampering or key mismatch)`);
|
|
11613
|
+
return MODEL;
|
|
11614
|
+
}
|
|
11615
|
+
try {
|
|
11616
|
+
const m = JSON.parse(c.modelJson);
|
|
11617
|
+
if (schemaValid(m)) return m;
|
|
11618
|
+
logger.debug(`cached model v${c.version} failed schema validation \u2014 using bundled`);
|
|
11619
|
+
} catch {
|
|
11620
|
+
logger.debug(`cached model v${c.version} JSON parse failed \u2014 using bundled`);
|
|
11621
|
+
}
|
|
11622
|
+
return MODEL;
|
|
11623
|
+
}
|
|
11624
|
+
|
|
11625
|
+
// src/analysis/FailureClusterer.ts
|
|
11626
|
+
init_cjs_shims();
|
|
11627
|
+
|
|
11628
|
+
// src/analysis/NaiveBayesClassifier.ts
|
|
11629
|
+
init_cjs_shims();
|
|
11630
|
+
|
|
11631
|
+
// src/analysis/tokenize.ts
|
|
11632
|
+
init_cjs_shims();
|
|
11633
|
+
function tokenize(text) {
|
|
11634
|
+
return text.toLowerCase().replace(/https?:\/\/\S+/gi, " URL ").replace(/[^a-zA-Z0-9\s]/g, " ").replace(/\b\d{3,}[a-z]*\b/g, " NUM ").split(/\s+/).filter((t) => t.length > 1);
|
|
11635
|
+
}
|
|
11636
|
+
|
|
11637
|
+
// src/analysis/NaiveBayesClassifier.ts
|
|
11638
|
+
function classify(model, text) {
|
|
11639
|
+
const tokens = tokenize(text);
|
|
11640
|
+
const vocabTokens = tokens.filter(
|
|
11641
|
+
(tok) => model.labels.some((l) => model.logLikelihoods[l]?.[tok] !== void 0)
|
|
11642
|
+
);
|
|
11643
|
+
if (vocabTokens.length === 0) {
|
|
11644
|
+
return { label: "unknown", margin: 0 };
|
|
11645
|
+
}
|
|
11646
|
+
const scores = {};
|
|
11647
|
+
for (const label of model.labels) {
|
|
11648
|
+
let s = model.priors[label];
|
|
11649
|
+
const oov = Math.log(model.alpha / (model.totals[label] + model.alpha * model.vocabSize));
|
|
11650
|
+
const ll = model.logLikelihoods[label];
|
|
11651
|
+
for (const tok of vocabTokens) {
|
|
11652
|
+
s += ll[tok] ?? oov;
|
|
11653
|
+
}
|
|
11654
|
+
scores[label] = s;
|
|
11655
|
+
}
|
|
11656
|
+
const ranked = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
|
11657
|
+
const topLog = ranked[0][1];
|
|
11658
|
+
const secondLog = ranked[1]?.[1] ?? topLog;
|
|
11659
|
+
const margin = (topLog - secondLog) / vocabTokens.length;
|
|
11660
|
+
return { label: ranked[0][0], margin };
|
|
11661
|
+
}
|
|
11662
|
+
|
|
11663
|
+
// src/analysis/constants.ts
|
|
11664
|
+
init_cjs_shims();
|
|
11665
|
+
var STRENGTH = {
|
|
11666
|
+
unknownMaxMargin: 0.05,
|
|
11667
|
+
collectMaxMargin: 0.1,
|
|
11668
|
+
harvestMinMargin: 0.25,
|
|
11669
|
+
moderateMinMargin: 0.1,
|
|
11670
|
+
strongMinMargin: 0.25
|
|
11671
|
+
};
|
|
11672
|
+
var STRENGTH_LEVELS = ["weak", "moderate", "strong"];
|
|
11673
|
+
var FLAKY_MARGIN = 999;
|
|
11674
|
+
|
|
11675
|
+
// src/analysis/FailureClusterer.ts
|
|
11676
|
+
var MAX_TESTS_PER_CLUSTER = 20;
|
|
11677
|
+
var MAX_MESSAGE_CHARS = 200;
|
|
11678
|
+
function strengthOf(margin) {
|
|
11679
|
+
if (margin >= STRENGTH.strongMinMargin) return "strong";
|
|
11680
|
+
if (margin >= STRENGTH.moderateMinMargin) return "moderate";
|
|
11681
|
+
return "weak";
|
|
11682
|
+
}
|
|
11683
|
+
function classifyInput(f) {
|
|
11684
|
+
const head = (f.error.stack ?? "").split("\n").slice(0, 3).join(" ");
|
|
11685
|
+
return `${f.error.message} ${head}`;
|
|
11686
|
+
}
|
|
11687
|
+
function topFrame(stack) {
|
|
11688
|
+
if (!stack) return void 0;
|
|
11689
|
+
for (const line of stack.split("\n")) {
|
|
11690
|
+
const t = line.trim();
|
|
11691
|
+
if (t.startsWith("at ") && !t.replace(/\\/g, "/").includes("node_modules/playwright")) return t;
|
|
11692
|
+
}
|
|
11693
|
+
return void 0;
|
|
11694
|
+
}
|
|
11695
|
+
function representativeMessage(message) {
|
|
11696
|
+
const firstLine = message.split("\n")[0];
|
|
11697
|
+
return firstLine.length > MAX_MESSAGE_CHARS ? `${firstLine.slice(0, MAX_MESSAGE_CHARS)}\u2026` : firstLine;
|
|
11698
|
+
}
|
|
11699
|
+
function classifyOne(f, model) {
|
|
11700
|
+
if (f.retryHistory.some((r) => r.status === "passed")) {
|
|
11701
|
+
return { category: "flaky-by-retry", margin: FLAKY_MARGIN, failure: f, frame: topFrame(f.error.stack) };
|
|
11702
|
+
}
|
|
11703
|
+
const { label, margin } = classify(model, classifyInput(f));
|
|
11704
|
+
return { category: label, margin, failure: f, frame: topFrame(f.error.stack) };
|
|
11705
|
+
}
|
|
11706
|
+
function classifyAll(failures, model) {
|
|
11707
|
+
return failures.map((f) => classifyOne(f, model));
|
|
11708
|
+
}
|
|
11709
|
+
function clusterClassified(classified) {
|
|
11710
|
+
const groups = /* @__PURE__ */ new Map();
|
|
11711
|
+
for (const c of classified) {
|
|
11712
|
+
const key = `${c.category}::${c.frame ?? "__no-stack__"}`;
|
|
11713
|
+
const bucket = groups.get(key);
|
|
11714
|
+
if (bucket) bucket.push(c);
|
|
11715
|
+
else groups.set(key, [c]);
|
|
11716
|
+
}
|
|
11717
|
+
const clusters = [];
|
|
11718
|
+
for (const members of groups.values()) {
|
|
11719
|
+
const margin = Math.max(...members.map((m) => m.margin));
|
|
11720
|
+
clusters.push({
|
|
11721
|
+
category: members[0].category,
|
|
11722
|
+
strength: strengthOf(margin),
|
|
11723
|
+
margin,
|
|
11724
|
+
count: members.length,
|
|
11725
|
+
tests: members.slice(0, MAX_TESTS_PER_CLUSTER).map((m) => m.failure.testTitle),
|
|
11726
|
+
testsTotal: members.length,
|
|
11727
|
+
representativeMessage: representativeMessage(members[0].failure.error.message),
|
|
11728
|
+
stackFrame: members[0].frame
|
|
11729
|
+
});
|
|
11730
|
+
}
|
|
11731
|
+
return clusters;
|
|
11732
|
+
}
|
|
11733
|
+
|
|
11734
|
+
// src/analysis/UnclassifiedCollector.ts
|
|
11735
|
+
init_cjs_shims();
|
|
11736
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
11737
|
+
var import_node_os2 = __toESM(require("os"));
|
|
11738
|
+
var import_node_path2 = __toESM(require("path"));
|
|
11739
|
+
|
|
11740
|
+
// src/analysis/feedback-csv.ts
|
|
11741
|
+
init_cjs_shims();
|
|
11742
|
+
var FEEDBACK_HEADER = "category,margin,text";
|
|
11743
|
+
function parseFeedbackRows(content) {
|
|
11744
|
+
return content.trim().split("\n").slice(1).filter(Boolean).map((line) => {
|
|
11745
|
+
const parts = line.split(",");
|
|
11746
|
+
return { category: parts[0], margin: parts[1], text: parts.slice(2).join(","), raw: line };
|
|
11747
|
+
});
|
|
11748
|
+
}
|
|
11749
|
+
function feedbackIdentity(category, text) {
|
|
11750
|
+
return `${category} ${text}`;
|
|
11751
|
+
}
|
|
11752
|
+
|
|
11753
|
+
// src/analysis/UnclassifiedCollector.ts
|
|
11754
|
+
var MAX_ROWS = 500;
|
|
11755
|
+
var FILENAME = "unclassified.csv";
|
|
11756
|
+
var URI_RE = /[a-z][\w+.-]*:\/\/\S+/gi;
|
|
11757
|
+
var EMAIL_RE = /\b[\w.+-]+@[\w.-]+\.\w+\b/g;
|
|
11758
|
+
var SECRET_RE = /\b(?=[A-Za-z0-9_+/=-]*[0-9])(?=[A-Za-z0-9_+/=-]*[A-Za-z])[A-Za-z0-9_+/=-]{16,}\b/g;
|
|
11759
|
+
var IP_RE = /\b\d{1,3}(?:\.\d{1,3}){3}\b/g;
|
|
11760
|
+
var WIN_PATH_RE = /[A-Za-z]:\\[^\s'"]+/g;
|
|
11761
|
+
var UNIX_PATH_RE = /(?:\/[\w.-]+){2,}/g;
|
|
11762
|
+
function redactForStorage(raw) {
|
|
11763
|
+
const redacted = raw.replace(URI_RE, " URL ").replace(EMAIL_RE, " EMAIL ").replace(SECRET_RE, " SECRET ").replace(IP_RE, " IP ").replace(WIN_PATH_RE, " PATH ").replace(UNIX_PATH_RE, " PATH ");
|
|
11764
|
+
return tokenize(redacted).join(" ");
|
|
11765
|
+
}
|
|
11766
|
+
function shouldCollect(c) {
|
|
11767
|
+
return c.category === "unknown" || c.margin <= STRENGTH.collectMaxMargin;
|
|
11768
|
+
}
|
|
11769
|
+
function tildeify(p) {
|
|
11770
|
+
const home = import_node_os2.default.homedir();
|
|
11771
|
+
return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
11772
|
+
}
|
|
11773
|
+
function collectUnclassified(classified, projectDir) {
|
|
11774
|
+
const rows = classified.filter(shouldCollect).map((c) => ({ text: redactForStorage(c.failure.error.message), category: c.category, margin: c.margin })).filter((r) => r.text.length > 0);
|
|
11775
|
+
if (rows.length === 0) return;
|
|
11776
|
+
const file = import_node_path2.default.join(projectDir, FILENAME);
|
|
11777
|
+
const firstTime = !import_node_fs2.default.existsSync(file);
|
|
11778
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
11779
|
+
if (!firstTime) {
|
|
11780
|
+
for (const r of parseFeedbackRows(import_node_fs2.default.readFileSync(file, "utf8"))) {
|
|
11781
|
+
byIdentity.set(feedbackIdentity(r.category, r.text), r.raw);
|
|
11782
|
+
}
|
|
11783
|
+
}
|
|
11784
|
+
let added = false;
|
|
11785
|
+
for (const r of rows) {
|
|
11786
|
+
const id = feedbackIdentity(r.category, r.text);
|
|
11787
|
+
byIdentity.delete(id);
|
|
11788
|
+
byIdentity.set(id, `${r.category},${r.margin},${r.text}`);
|
|
11789
|
+
added = true;
|
|
11790
|
+
}
|
|
11791
|
+
if (!added) return;
|
|
11792
|
+
const all = [...byIdentity.values()].slice(-MAX_ROWS);
|
|
11793
|
+
import_node_fs2.default.mkdirSync(projectDir, { recursive: true });
|
|
11794
|
+
import_node_fs2.default.writeFileSync(file, `${FEEDBACK_HEADER}
|
|
11795
|
+
${all.join("\n")}
|
|
11796
|
+
`, "utf8");
|
|
11797
|
+
if (firstTime) {
|
|
11798
|
+
logger.info(
|
|
11799
|
+
`Unrecognized failures collected locally at ${tildeify(file)} to improve offline classification. Local only - never sent anywhere. Disable: failureAnalysis.collectUnclassified=false`
|
|
11800
|
+
);
|
|
11801
|
+
}
|
|
11802
|
+
}
|
|
11803
|
+
|
|
11804
|
+
// src/utils/paths.ts
|
|
11805
|
+
init_cjs_shims();
|
|
11806
|
+
var import_node_os3 = __toESM(require("os"));
|
|
11807
|
+
var import_node_path3 = __toESM(require("path"));
|
|
11808
|
+
var import_node_crypto2 = __toESM(require("crypto"));
|
|
11809
|
+
function resolveProjectDir(options, cwd) {
|
|
11810
|
+
const projectKey = import_node_crypto2.default.createHash("sha256").update(cwd + (options.outputFile ?? "")).digest("hex").slice(0, 8);
|
|
11811
|
+
return import_node_path3.default.join(import_node_os3.default.homedir(), ".reportforge", projectKey);
|
|
11812
|
+
}
|
|
11813
|
+
|
|
11814
|
+
// src/analysis/index.ts
|
|
11815
|
+
var DEFAULTS = { maxClusters: 10, minStrength: "weak", maxFailuresToAnalyse: 500 };
|
|
11816
|
+
var STRENGTH_RANK = Object.fromEntries(
|
|
11817
|
+
STRENGTH_LEVELS.map((level, i) => [level, i])
|
|
11818
|
+
);
|
|
11819
|
+
function analyseClassified(classified, totalFailures, opts = {}) {
|
|
11820
|
+
const maxClusters = opts.maxClusters ?? DEFAULTS.maxClusters;
|
|
11821
|
+
const minRank = STRENGTH_RANK[opts.minStrength ?? DEFAULTS.minStrength];
|
|
11822
|
+
const cap = opts.maxFailuresToAnalyse ?? DEFAULTS.maxFailuresToAnalyse;
|
|
11823
|
+
const clusters = clusterClassified(classified).filter((c) => STRENGTH_RANK[c.strength] >= minRank).sort((a, b) => b.count - a.count).slice(0, maxClusters);
|
|
11824
|
+
return {
|
|
11825
|
+
clusters,
|
|
11826
|
+
totalFailures,
|
|
11827
|
+
analysedCount: classified.length,
|
|
11828
|
+
truncated: totalFailures > cap,
|
|
11829
|
+
// dominantCategory is part of the AnalysisResult contract; reserved for
|
|
11830
|
+
// consumers (e.g. a future executive badge / notify summary). Not rendered
|
|
11831
|
+
// by the v1 partials.
|
|
11832
|
+
dominantCategory: dominantCategory(clusters),
|
|
11833
|
+
oneLiner: buildOneLiner(clusters)
|
|
11834
|
+
};
|
|
11835
|
+
}
|
|
11836
|
+
function templatesConsumeAnalysis(options) {
|
|
11837
|
+
if (options.templatePath && options.templatePath.length > 0) return true;
|
|
11838
|
+
const templates = Array.isArray(options.template) ? options.template : [options.template];
|
|
11839
|
+
return templates.some((t) => t === "detailed" || t === "executive");
|
|
11840
|
+
}
|
|
11841
|
+
function runFailureAnalysis(reportData, options, cwd) {
|
|
11842
|
+
const fa = options.failureAnalysis;
|
|
11843
|
+
if (reportData.stats.failed === 0 || !fa.enabled) return;
|
|
11844
|
+
const cap = fa.maxFailuresToAnalyse;
|
|
11845
|
+
const analysed = reportData.failures.slice(0, cap);
|
|
11846
|
+
const model = loadModel(fa.autoUpdateModel);
|
|
11847
|
+
const classified = classifyAll(analysed, model);
|
|
11848
|
+
if (templatesConsumeAnalysis(options)) {
|
|
11849
|
+
reportData.analysis = analyseClassified(classified, reportData.failures.length, fa);
|
|
11850
|
+
}
|
|
11851
|
+
if (fa.collectUnclassified) {
|
|
11852
|
+
collectUnclassified(classified, resolveProjectDir(options, cwd));
|
|
11853
|
+
}
|
|
11854
|
+
}
|
|
11855
|
+
|
|
11503
11856
|
// src/demo/cli.ts
|
|
11504
11857
|
var DEMO_LICENSE_INFO2 = {
|
|
11505
11858
|
plan: "subscription",
|
|
@@ -11529,8 +11882,9 @@ async function run() {
|
|
|
11529
11882
|
console.log(` Template : ${template}`);
|
|
11530
11883
|
console.log(` Output : ${outputFile}`);
|
|
11531
11884
|
const reportData = buildDemoReportData(template);
|
|
11532
|
-
const options = parseOptions({ template, outputFile });
|
|
11885
|
+
const options = parseOptions({ template, outputFile, failureAnalysis: { collectUnclassified: false } });
|
|
11533
11886
|
const generator = new PdfGenerator();
|
|
11887
|
+
runFailureAnalysis(reportData, options, process.cwd());
|
|
11534
11888
|
const paths = await generator.generateAll(reportData, options, DEMO_LICENSE_INFO2);
|
|
11535
11889
|
if (paths.length > 0) {
|
|
11536
11890
|
console.log(`
|