@reportforge/playwright-pdf 0.22.0 → 0.23.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/index.js CHANGED
@@ -1221,13 +1221,13 @@ var require_ast = __commonJS({
1221
1221
  helperExpression: function helperExpression(node) {
1222
1222
  return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
1223
1223
  },
1224
- scopedId: function scopedId(path16) {
1225
- return /^\.|this\b/.test(path16.original);
1224
+ scopedId: function scopedId(path15) {
1225
+ return /^\.|this\b/.test(path15.original);
1226
1226
  },
1227
1227
  // an ID is simple if it only has one part, and that part is not
1228
1228
  // `..` or `this`.
1229
- simpleId: function simpleId(path16) {
1230
- return path16.parts.length === 1 && !AST.helpers.scopedId(path16) && !path16.depth;
1229
+ simpleId: function simpleId(path15) {
1230
+ return path15.parts.length === 1 && !AST.helpers.scopedId(path15) && !path15.depth;
1231
1231
  }
1232
1232
  }
1233
1233
  };
@@ -2301,12 +2301,12 @@ var require_helpers2 = __commonJS({
2301
2301
  loc
2302
2302
  };
2303
2303
  }
2304
- function prepareMustache(path16, params, hash, open, strip, locInfo) {
2304
+ function prepareMustache(path15, params, hash, open, strip, locInfo) {
2305
2305
  var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
2306
2306
  var decorator = /\*/.test(open);
2307
2307
  return {
2308
2308
  type: decorator ? "Decorator" : "MustacheStatement",
2309
- path: path16,
2309
+ path: path15,
2310
2310
  params,
2311
2311
  hash,
2312
2312
  escaped,
@@ -2626,9 +2626,9 @@ var require_compiler = __commonJS({
2626
2626
  },
2627
2627
  DecoratorBlock: function DecoratorBlock(decorator) {
2628
2628
  var program = decorator.program && this.compileProgram(decorator.program);
2629
- var params = this.setupFullMustacheParams(decorator, program, void 0), path16 = decorator.path;
2629
+ var params = this.setupFullMustacheParams(decorator, program, void 0), path15 = decorator.path;
2630
2630
  this.useDecorators = true;
2631
- this.opcode("registerDecorator", params.length, path16.original);
2631
+ this.opcode("registerDecorator", params.length, path15.original);
2632
2632
  },
2633
2633
  PartialStatement: function PartialStatement(partial) {
2634
2634
  this.usePartial = true;
@@ -2692,46 +2692,46 @@ var require_compiler = __commonJS({
2692
2692
  }
2693
2693
  },
2694
2694
  ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
2695
- var path16 = sexpr.path, name = path16.parts[0], isBlock = program != null || inverse != null;
2696
- this.opcode("getContext", path16.depth);
2695
+ var path15 = sexpr.path, name = path15.parts[0], isBlock = program != null || inverse != null;
2696
+ this.opcode("getContext", path15.depth);
2697
2697
  this.opcode("pushProgram", program);
2698
2698
  this.opcode("pushProgram", inverse);
2699
- path16.strict = true;
2700
- this.accept(path16);
2699
+ path15.strict = true;
2700
+ this.accept(path15);
2701
2701
  this.opcode("invokeAmbiguous", name, isBlock);
2702
2702
  },
2703
2703
  simpleSexpr: function simpleSexpr(sexpr) {
2704
- var path16 = sexpr.path;
2705
- path16.strict = true;
2706
- this.accept(path16);
2704
+ var path15 = sexpr.path;
2705
+ path15.strict = true;
2706
+ this.accept(path15);
2707
2707
  this.opcode("resolvePossibleLambda");
2708
2708
  },
2709
2709
  helperSexpr: function helperSexpr(sexpr, program, inverse) {
2710
- var params = this.setupFullMustacheParams(sexpr, program, inverse), path16 = sexpr.path, name = path16.parts[0];
2710
+ var params = this.setupFullMustacheParams(sexpr, program, inverse), path15 = sexpr.path, name = path15.parts[0];
2711
2711
  if (this.options.knownHelpers[name]) {
2712
2712
  this.opcode("invokeKnownHelper", params.length, name);
2713
2713
  } else if (this.options.knownHelpersOnly) {
2714
2714
  throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
2715
2715
  } else {
2716
- path16.strict = true;
2717
- path16.falsy = true;
2718
- this.accept(path16);
2719
- this.opcode("invokeHelper", params.length, path16.original, _ast2["default"].helpers.simpleId(path16));
2716
+ path15.strict = true;
2717
+ path15.falsy = true;
2718
+ this.accept(path15);
2719
+ this.opcode("invokeHelper", params.length, path15.original, _ast2["default"].helpers.simpleId(path15));
2720
2720
  }
2721
2721
  },
2722
- PathExpression: function PathExpression(path16) {
2723
- this.addDepth(path16.depth);
2724
- this.opcode("getContext", path16.depth);
2725
- var name = path16.parts[0], scoped = _ast2["default"].helpers.scopedId(path16), blockParamId = !path16.depth && !scoped && this.blockParamIndex(name);
2722
+ PathExpression: function PathExpression(path15) {
2723
+ this.addDepth(path15.depth);
2724
+ this.opcode("getContext", path15.depth);
2725
+ var name = path15.parts[0], scoped = _ast2["default"].helpers.scopedId(path15), blockParamId = !path15.depth && !scoped && this.blockParamIndex(name);
2726
2726
  if (blockParamId) {
2727
- this.opcode("lookupBlockParam", blockParamId, path16.parts);
2727
+ this.opcode("lookupBlockParam", blockParamId, path15.parts);
2728
2728
  } else if (!name) {
2729
2729
  this.opcode("pushContext");
2730
- } else if (path16.data) {
2730
+ } else if (path15.data) {
2731
2731
  this.options.data = true;
2732
- this.opcode("lookupData", path16.depth, path16.parts, path16.strict);
2732
+ this.opcode("lookupData", path15.depth, path15.parts, path15.strict);
2733
2733
  } else {
2734
- this.opcode("lookupOnContext", path16.parts, path16.falsy, path16.strict, scoped);
2734
+ this.opcode("lookupOnContext", path15.parts, path15.falsy, path15.strict, scoped);
2735
2735
  }
2736
2736
  },
2737
2737
  StringLiteral: function StringLiteral(string) {
@@ -3087,16 +3087,16 @@ var require_util = __commonJS({
3087
3087
  }
3088
3088
  exports2.urlGenerate = urlGenerate;
3089
3089
  function normalize(aPath) {
3090
- var path16 = aPath;
3090
+ var path15 = aPath;
3091
3091
  var url = urlParse(aPath);
3092
3092
  if (url) {
3093
3093
  if (!url.path) {
3094
3094
  return aPath;
3095
3095
  }
3096
- path16 = url.path;
3096
+ path15 = url.path;
3097
3097
  }
3098
- var isAbsolute = exports2.isAbsolute(path16);
3099
- var parts = path16.split(/\/+/);
3098
+ var isAbsolute = exports2.isAbsolute(path15);
3099
+ var parts = path15.split(/\/+/);
3100
3100
  for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
3101
3101
  part = parts[i];
3102
3102
  if (part === ".") {
@@ -3113,15 +3113,15 @@ var require_util = __commonJS({
3113
3113
  }
3114
3114
  }
3115
3115
  }
3116
- path16 = parts.join("/");
3117
- if (path16 === "") {
3118
- path16 = isAbsolute ? "/" : ".";
3116
+ path15 = parts.join("/");
3117
+ if (path15 === "") {
3118
+ path15 = isAbsolute ? "/" : ".";
3119
3119
  }
3120
3120
  if (url) {
3121
- url.path = path16;
3121
+ url.path = path15;
3122
3122
  return urlGenerate(url);
3123
3123
  }
3124
- return path16;
3124
+ return path15;
3125
3125
  }
3126
3126
  exports2.normalize = normalize;
3127
3127
  function join(aRoot, aPath) {
@@ -5924,8 +5924,8 @@ var require_printer = __commonJS({
5924
5924
  return this.accept(sexpr.path) + " " + params + hash;
5925
5925
  };
5926
5926
  PrintVisitor.prototype.PathExpression = function(id) {
5927
- var path16 = id.parts.join("/");
5928
- return (id.data ? "@" : "") + "PATH:" + path16;
5927
+ var path15 = id.parts.join("/");
5928
+ return (id.data ? "@" : "") + "PATH:" + path15;
5929
5929
  };
5930
5930
  PrintVisitor.prototype.StringLiteral = function(string) {
5931
5931
  return '"' + string.value + '"';
@@ -6045,7 +6045,7 @@ var require_win32 = __commonJS({
6045
6045
  "node_modules/chrome-finder/lib/win32.js"(exports2, module2) {
6046
6046
  "use strict";
6047
6047
  init_cjs_shims();
6048
- var path16 = require("path");
6048
+ var path15 = require("path");
6049
6049
  var { canAccess } = require_util2();
6050
6050
  function win32() {
6051
6051
  const installations = [];
@@ -6059,7 +6059,7 @@ var require_win32 = __commonJS({
6059
6059
  const prefixes = [process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"]];
6060
6060
  prefixes.forEach((prefix) => suffixes.forEach((suffix) => {
6061
6061
  if (prefix) {
6062
- const chromePath = path16.join(prefix, suffix);
6062
+ const chromePath = path15.join(prefix, suffix);
6063
6063
  if (canAccess(chromePath)) {
6064
6064
  installations.push(chromePath);
6065
6065
  }
@@ -6077,7 +6077,7 @@ var require_darwin = __commonJS({
6077
6077
  "use strict";
6078
6078
  init_cjs_shims();
6079
6079
  var { execSync: execSync3 } = require("child_process");
6080
- var path16 = require("path");
6080
+ var path15 = require("path");
6081
6081
  var { canAccess, newLineRegex, sort } = require_util2();
6082
6082
  function darwin() {
6083
6083
  const suffixes = ["/Contents/MacOS/Google Chrome Canary", "/Contents/MacOS/Google Chrome", "/Contents/MacOS/Chromium"];
@@ -6087,7 +6087,7 @@ var require_darwin = __commonJS({
6087
6087
  ${LSREGISTER} -dump | grep -E -i -o '/.+(google chrome( canary)?|chromium)\\.app(\\s|$)' | grep -E -v 'Caches|TimeMachine|Temporary|/Volumes|\\.Trash'
6088
6088
  `).toString().split(newLineRegex).forEach((inst) => {
6089
6089
  suffixes.forEach((suffix) => {
6090
- const execPath = path16.join(inst.trim(), suffix);
6090
+ const execPath = path15.join(inst.trim(), suffix);
6091
6091
  if (canAccess(execPath)) {
6092
6092
  installations.push(execPath);
6093
6093
  }
@@ -6115,8 +6115,8 @@ var require_linux = __commonJS({
6115
6115
  "node_modules/chrome-finder/lib/linux.js"(exports2, module2) {
6116
6116
  "use strict";
6117
6117
  init_cjs_shims();
6118
- var { execSync: execSync3, execFileSync: execFileSync2 } = require("child_process");
6119
- var path16 = require("path");
6118
+ var { execSync: execSync3, execFileSync } = require("child_process");
6119
+ var path15 = require("path");
6120
6120
  var fs16 = require("fs");
6121
6121
  var { canAccess, sort, isExecutable, newLineRegex } = require_util2();
6122
6122
  function findChromeExecutablesForLinuxDesktop(folder) {
@@ -6134,7 +6134,7 @@ var require_linux = __commonJS({
6134
6134
  function linux() {
6135
6135
  let installations = [];
6136
6136
  const desktopInstallationFolders = [
6137
- path16.join(require("os").homedir(), ".local/share/applications/"),
6137
+ path15.join(require("os").homedir(), ".local/share/applications/"),
6138
6138
  "/usr/share/applications/"
6139
6139
  ];
6140
6140
  desktopInstallationFolders.forEach((folder) => {
@@ -6172,7 +6172,7 @@ var require_linux = __commonJS({
6172
6172
  return;
6173
6173
  }
6174
6174
  try {
6175
- const chromePath = execFileSync2("which", [executable]).toString().split(newLineRegex)[0];
6175
+ const chromePath = execFileSync("which", [executable]).toString().split(newLineRegex)[0];
6176
6176
  if (canAccess(chromePath)) {
6177
6177
  installations.push(chromePath);
6178
6178
  }
@@ -6749,8 +6749,8 @@ function getErrorMap() {
6749
6749
  // node_modules/zod/v3/helpers/parseUtil.js
6750
6750
  init_cjs_shims();
6751
6751
  var makeIssue = (params) => {
6752
- const { data, path: path16, errorMaps, issueData } = params;
6753
- const fullPath = [...path16, ...issueData.path || []];
6752
+ const { data, path: path15, errorMaps, issueData } = params;
6753
+ const fullPath = [...path15, ...issueData.path || []];
6754
6754
  const fullIssue = {
6755
6755
  ...issueData,
6756
6756
  path: fullPath
@@ -6870,11 +6870,11 @@ var errorUtil;
6870
6870
 
6871
6871
  // node_modules/zod/v3/types.js
6872
6872
  var ParseInputLazyPath = class {
6873
- constructor(parent, value, path16, key) {
6873
+ constructor(parent, value, path15, key) {
6874
6874
  this._cachedPath = [];
6875
6875
  this.parent = parent;
6876
6876
  this.data = value;
6877
- this._path = path16;
6877
+ this._path = path15;
6878
6878
  this._key = key;
6879
6879
  }
6880
6880
  get path() {
@@ -10519,6 +10519,21 @@ var liveConfigSchema = external_exports.object({
10519
10519
  // Debounce window for batched flushes (ms).
10520
10520
  flushMs: external_exports.number().int().min(500).max(1e4).default(2e3)
10521
10521
  }).default({});
10522
+ var redactConfigSchema = external_exports.object({
10523
+ enabled: external_exports.boolean().default(false),
10524
+ builtins: external_exports.boolean().default(true),
10525
+ patterns: external_exports.array(
10526
+ external_exports.string().refine((v) => {
10527
+ try {
10528
+ new RegExp(v);
10529
+ return true;
10530
+ } catch {
10531
+ return false;
10532
+ }
10533
+ }, "Each redact.patterns entry must be a valid regular expression")
10534
+ ).default([]),
10535
+ mask: external_exports.string().default("[REDACTED]")
10536
+ }).default({});
10522
10537
  var sectionFlagsShape = Object.fromEntries(
10523
10538
  SECTION_KEYS.map((k) => [k, external_exports.boolean().optional()])
10524
10539
  );
@@ -10539,7 +10554,7 @@ var ReporterOptionsSchema = external_exports.object({
10539
10554
  primaryColor: hexColor.optional().default(DEFAULT_OPTIONS.primaryColor),
10540
10555
  accentColor: hexColor.optional().default(DEFAULT_OPTIONS.accentColor),
10541
10556
  watermark: external_exports.string().optional(),
10542
- pdfPassword: external_exports.string().optional(),
10557
+ pdfPassword: external_exports.string().min(1, "pdfPassword must not be empty. Omit the option (or leave the env var unset) to disable encryption.").optional(),
10543
10558
  reportTitle: external_exports.string().optional().default(DEFAULT_OPTIONS.reportTitle),
10544
10559
  projectName: external_exports.string().optional(),
10545
10560
  open: external_exports.boolean().optional().default(DEFAULT_OPTIONS.open),
@@ -10572,6 +10587,7 @@ var ReporterOptionsSchema = external_exports.object({
10572
10587
  }, "requirementTagPattern must be a valid regular expression").optional().default(DEFAULT_OPTIONS.requirementTagPattern),
10573
10588
  failureAnalysis: failureAnalysisConfigSchema,
10574
10589
  capture: captureConfigSchema,
10590
+ redact: redactConfigSchema,
10575
10591
  live: liveConfigSchema,
10576
10592
  templatePath: external_exports.union([
10577
10593
  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"),
@@ -12614,7 +12630,7 @@ var ShardMerger = class {
12614
12630
  // src/pdf/PdfGenerator.ts
12615
12631
  init_cjs_shims();
12616
12632
  var import_fs11 = __toESM(require("fs"));
12617
- var import_path8 = __toESM(require("path"));
12633
+ var import_path7 = __toESM(require("path"));
12618
12634
 
12619
12635
  // src/templates/engine.ts
12620
12636
  init_cjs_shims();
@@ -13149,49 +13165,38 @@ var ChartRenderer = class {
13149
13165
 
13150
13166
  // src/pdf/PdfEncryptor.ts
13151
13167
  init_cjs_shims();
13152
- var import_child_process3 = require("child_process");
13153
13168
  var import_fs8 = __toESM(require("fs"));
13154
- var import_path5 = __toESM(require("path"));
13155
- var import_os5 = __toESM(require("os"));
13156
- var import_crypto7 = __toESM(require("crypto"));
13169
+ var import_pdf_lib = require("@cantoo/pdf-lib");
13170
+ var AES256_HEADER_MINOR = "7ext3";
13157
13171
  var PdfEncryptor = class {
13158
13172
  async encrypt(pdfPath, password) {
13159
13173
  if (!password) return;
13160
- if (!this.isQpdfAvailable()) {
13161
- logger.warn(
13162
- "pdfPassword is set but qpdf is not installed \u2014 PDF will NOT be encrypted. Install qpdf: https://qpdf.readthedocs.io/en/stable/installation.html"
13163
- );
13164
- return;
13165
- }
13166
- const tmpPath = import_path5.default.join(import_os5.default.tmpdir(), `rf-encrypted-${Date.now()}.pdf`);
13167
- const argFile = import_path5.default.join(import_os5.default.tmpdir(), `rf-qpdf-args-${import_crypto7.default.randomBytes(8).toString("hex")}`);
13168
13174
  try {
13169
- const args = ["--encrypt", password, password, "256", "--", pdfPath, tmpPath];
13170
- import_fs8.default.writeFileSync(argFile, args.join("\n"), { mode: 384 });
13171
- (0, import_child_process3.execFileSync)("qpdf", [`@${argFile}`], { stdio: "pipe" });
13172
- import_fs8.default.renameSync(tmpPath, pdfPath);
13173
- logger.info(`PDF encrypted with password.`);
13175
+ const doc = await import_pdf_lib.PDFDocument.load(await import_fs8.default.promises.readFile(pdfPath));
13176
+ doc.context.header = import_pdf_lib.PDFHeader.forVersion(1, AES256_HEADER_MINOR);
13177
+ doc.encrypt({
13178
+ userPassword: password,
13179
+ ownerPassword: password,
13180
+ permissions: { printing: "highResolution", copying: false, modifying: false }
13181
+ });
13182
+ const encryptedBytes = await doc.save();
13183
+ const tmpPath = `${pdfPath}.rf-enc-tmp`;
13184
+ try {
13185
+ await import_fs8.default.promises.writeFile(tmpPath, encryptedBytes);
13186
+ await import_fs8.default.promises.rename(tmpPath, pdfPath);
13187
+ } finally {
13188
+ import_fs8.default.rmSync(tmpPath, { force: true });
13189
+ }
13190
+ logger.info("PDF encrypted with password (AES-256).");
13174
13191
  } catch (e) {
13175
- throw new Error(`qpdf encryption failed: ${e.message}`);
13176
- } finally {
13177
- if (import_fs8.default.existsSync(tmpPath)) import_fs8.default.unlinkSync(tmpPath);
13178
- import_fs8.default.rmSync(argFile, { force: true });
13179
- }
13180
- }
13181
- isQpdfAvailable() {
13182
- try {
13183
- (0, import_child_process3.execFileSync)("qpdf", ["--version"], { stdio: "pipe" });
13184
- return true;
13185
- } catch (err) {
13186
- logger.debug("qpdf probe failed \u2014 treating as not installed", { error: err.message });
13187
- return false;
13192
+ throw new Error(`PDF encryption failed: ${e.message}`);
13188
13193
  }
13189
13194
  }
13190
13195
  };
13191
13196
 
13192
13197
  // src/filename/FilenameResolver.ts
13193
13198
  init_cjs_shims();
13194
- var import_path6 = __toESM(require("path"));
13199
+ var import_path5 = __toESM(require("path"));
13195
13200
  var import_fs9 = __toESM(require("fs"));
13196
13201
  var MAX_BRANCH_LENGTH = 50;
13197
13202
  var FilenameResolver = class {
@@ -13209,8 +13214,8 @@ var FilenameResolver = class {
13209
13214
  const branch = this.sanitiseBranch(ctx.branch);
13210
13215
  const status = ctx.status;
13211
13216
  const resolved = template.replace(/\{date\}/g, date).replace(/\{branch\}/g, branch).replace(/\{status\}/g, status);
13212
- const absolute = import_path6.default.isAbsolute(resolved) ? resolved : import_path6.default.resolve(process.cwd(), resolved);
13213
- import_fs9.default.mkdirSync(import_path6.default.dirname(absolute), { recursive: true });
13217
+ const absolute = import_path5.default.isAbsolute(resolved) ? resolved : import_path5.default.resolve(process.cwd(), resolved);
13218
+ import_fs9.default.mkdirSync(import_path5.default.dirname(absolute), { recursive: true });
13214
13219
  return absolute;
13215
13220
  }
13216
13221
  /**
@@ -13222,7 +13227,7 @@ var FilenameResolver = class {
13222
13227
  * // → '/out/2026-report-executive.pdf'
13223
13228
  */
13224
13229
  injectTemplateSuffix(outputPath, suffix) {
13225
- const ext = import_path6.default.extname(outputPath);
13230
+ const ext = import_path5.default.extname(outputPath);
13226
13231
  const base = outputPath.slice(0, outputPath.length - ext.length);
13227
13232
  return `${base}-${suffix}${ext}`;
13228
13233
  }
@@ -13281,7 +13286,12 @@ var PRESETS = {
13281
13286
  // src/pdf/FailurePaginator.ts
13282
13287
  init_cjs_shims();
13283
13288
  var import_fs10 = __toESM(require("fs"));
13284
- var import_path7 = __toESM(require("path"));
13289
+ var import_path6 = __toESM(require("path"));
13290
+ function sidecarPathFor(pdfPath) {
13291
+ const dir = import_path6.default.dirname(pdfPath);
13292
+ const base = import_path6.default.basename(pdfPath, import_path6.default.extname(pdfPath));
13293
+ return import_path6.default.join(dir, `${base}-failures.json`);
13294
+ }
13285
13295
  var FailurePaginator = class {
13286
13296
  async paginate(failures, maxInline, pdfOutputPath) {
13287
13297
  if (failures.length <= maxInline) {
@@ -13289,7 +13299,7 @@ var FailurePaginator = class {
13289
13299
  }
13290
13300
  const inline = failures.slice(0, maxInline);
13291
13301
  const overflow = failures.slice(maxInline);
13292
- const sidecarPath = this.sidecarPathFor(pdfOutputPath);
13302
+ const sidecarPath = sidecarPathFor(pdfOutputPath);
13293
13303
  try {
13294
13304
  await import_fs10.default.promises.writeFile(
13295
13305
  sidecarPath,
@@ -13297,7 +13307,7 @@ var FailurePaginator = class {
13297
13307
  "utf8"
13298
13308
  );
13299
13309
  logger.info(
13300
- `Emitted ${overflow.length} overflow failures \u2192 ${import_path7.default.basename(sidecarPath)}`
13310
+ `Emitted ${overflow.length} overflow failures \u2192 ${import_path6.default.basename(sidecarPath)}`
13301
13311
  );
13302
13312
  } catch (e) {
13303
13313
  logger.warn(`Could not write failure sidecar: ${e.message}`);
@@ -13305,11 +13315,6 @@ var FailurePaginator = class {
13305
13315
  }
13306
13316
  return { inline, overflowCount: overflow.length, sidecarPath };
13307
13317
  }
13308
- sidecarPathFor(pdfPath) {
13309
- const dir = import_path7.default.dirname(pdfPath);
13310
- const base = import_path7.default.basename(pdfPath, import_path7.default.extname(pdfPath));
13311
- return import_path7.default.join(dir, `${base}-failures.json`);
13312
- }
13313
13318
  /**
13314
13319
  * Drops the inlined image bytes — a 150-entry overflow would otherwise
13315
13320
  * produce a 40 MB JSON. The path still references the on-disk screenshot
@@ -13324,6 +13329,21 @@ var FailurePaginator = class {
13324
13329
  }
13325
13330
  };
13326
13331
 
13332
+ // src/pdf/sidecar-crypto.ts
13333
+ init_cjs_shims();
13334
+ var import_crypto7 = __toESM(require("crypto"));
13335
+ var MAGIC = Buffer.from("RFENC1");
13336
+ var SALT_LEN = 16;
13337
+ var IV_LEN = 12;
13338
+ function encryptSidecar(plain, password) {
13339
+ const salt = import_crypto7.default.randomBytes(SALT_LEN);
13340
+ const key = import_crypto7.default.scryptSync(password, salt, 32, { N: 16384, r: 8, p: 1 });
13341
+ const iv = import_crypto7.default.randomBytes(IV_LEN);
13342
+ const cipher = import_crypto7.default.createCipheriv("aes-256-gcm", key, iv);
13343
+ const enc = Buffer.concat([cipher.update(plain), cipher.final()]);
13344
+ return Buffer.concat([MAGIC, salt, iv, cipher.getAuthTag(), enc]);
13345
+ }
13346
+
13327
13347
  // src/pdf/PdfGenerator.ts
13328
13348
  var PdfGenerator = class {
13329
13349
  constructor() {
@@ -13421,7 +13441,7 @@ var PdfGenerator = class {
13421
13441
  const templateId = source.kind === "builtin" ? source.id : "detailed";
13422
13442
  const templateData = { ...data, meta: { ...data.meta, template: templateId } };
13423
13443
  const templateOptions = { ...options, template: templateId };
13424
- await this.renderPdf(templateData, templateOptions, source, compression, outputPath, page);
13444
+ let { sidecarPath } = await this.renderPdf(templateData, templateOptions, source, compression, outputPath, page);
13425
13445
  const capBytes = (options.maxFileSizeMb ?? 0) * 1024 * 1024;
13426
13446
  if (capBytes > 0) {
13427
13447
  const sizeBytes = (await import_fs11.default.promises.stat(outputPath)).size;
@@ -13431,7 +13451,8 @@ var PdfGenerator = class {
13431
13451
  logger.warn(
13432
13452
  `PDF size ${fmtMb(sizeBytes)} exceeds cap ${fmtMb(capBytes)} \u2014 re-rendering (jpeg-q=${tighter.quality}, max-w=${tighter.maxWidth}px, inline-failures\u2264${tighter.maxInlineFailures}).`
13433
13453
  );
13434
- await this.renderPdf(templateData, templateOptions, source, tighter, outputPath, page);
13454
+ const retuned = await this.renderPdf(templateData, templateOptions, source, tighter, outputPath, page);
13455
+ sidecarPath = retuned.sidecarPath ?? sidecarPath;
13435
13456
  const finalSize = (await import_fs11.default.promises.stat(outputPath)).size;
13436
13457
  if (finalSize > capBytes) {
13437
13458
  logger.warn(
@@ -13443,7 +13464,36 @@ var PdfGenerator = class {
13443
13464
  }
13444
13465
  }
13445
13466
  if (options.pdfPassword) {
13446
- await this.pdfEncryptor.encrypt(outputPath, options.pdfPassword);
13467
+ let pdfEncrypted = false;
13468
+ try {
13469
+ await this.pdfEncryptor.encrypt(outputPath, options.pdfPassword);
13470
+ pdfEncrypted = true;
13471
+ if (sidecarPath && import_fs11.default.existsSync(sidecarPath)) {
13472
+ const blob = encryptSidecar(await import_fs11.default.promises.readFile(sidecarPath), options.pdfPassword);
13473
+ await import_fs11.default.promises.writeFile(`${sidecarPath}.enc`, blob);
13474
+ await import_fs11.default.promises.unlink(sidecarPath);
13475
+ logger.info(`Overflow sidecar encrypted \u2192 ${import_path7.default.basename(sidecarPath)}.enc`);
13476
+ } else {
13477
+ const staleSidecarPath = sidecarPathFor(outputPath);
13478
+ if (import_fs11.default.existsSync(staleSidecarPath)) {
13479
+ import_fs11.default.rmSync(staleSidecarPath, { force: true });
13480
+ logger.info(`Removed stale plaintext sidecar: ${import_path7.default.basename(staleSidecarPath)}`);
13481
+ }
13482
+ }
13483
+ } catch (err) {
13484
+ const sidecarTargets = sidecarPath ? [sidecarPath, `${sidecarPath}.enc`] : [];
13485
+ const cleanupTargets = pdfEncrypted ? sidecarTargets : [outputPath, ...sidecarTargets];
13486
+ for (const target of cleanupTargets) {
13487
+ try {
13488
+ import_fs11.default.rmSync(target, { force: true });
13489
+ } catch (cleanupErr) {
13490
+ logger.warn(
13491
+ `Could not remove leftover file after encryption failure: ${target} (${cleanupErr.message})`
13492
+ );
13493
+ }
13494
+ }
13495
+ throw err;
13496
+ }
13447
13497
  }
13448
13498
  const finalSizeMb = fmtMb((await import_fs11.default.promises.stat(outputPath)).size);
13449
13499
  logger.info(`PDF report generated: ${outputPath} (${finalSizeMb})`);
@@ -13458,17 +13508,17 @@ var PdfGenerator = class {
13458
13508
  if (options.templatePath && options.templatePath.length > 0) {
13459
13509
  const byAbsolutePath = /* @__PURE__ */ new Map();
13460
13510
  for (const p of options.templatePath) {
13461
- const absolutePath = import_path8.default.isAbsolute(p) ? p : import_path8.default.resolve(process.cwd(), p);
13511
+ const absolutePath = import_path7.default.isAbsolute(p) ? p : import_path7.default.resolve(process.cwd(), p);
13462
13512
  if (!byAbsolutePath.has(absolutePath)) byAbsolutePath.set(absolutePath, p);
13463
13513
  }
13464
13514
  const labelCounts = /* @__PURE__ */ new Map();
13465
13515
  for (const rawPath of byAbsolutePath.values()) {
13466
- const label = import_path8.default.basename(rawPath, ".hbs");
13516
+ const label = import_path7.default.basename(rawPath, ".hbs");
13467
13517
  labelCounts.set(label, (labelCounts.get(label) ?? 0) + 1);
13468
13518
  }
13469
13519
  const seenPerLabel = /* @__PURE__ */ new Map();
13470
13520
  return Array.from(byAbsolutePath.entries()).map(([absolutePath, rawPath]) => {
13471
- const baseLabel = import_path8.default.basename(rawPath, ".hbs");
13521
+ const baseLabel = import_path7.default.basename(rawPath, ".hbs");
13472
13522
  let label = baseLabel;
13473
13523
  if ((labelCounts.get(baseLabel) ?? 0) > 1) {
13474
13524
  const n = (seenPerLabel.get(baseLabel) ?? 0) + 1;
@@ -13528,7 +13578,7 @@ var PdfGenerator = class {
13528
13578
  ...data,
13529
13579
  failures: paginated.inline,
13530
13580
  overflowFailureCount: paginated.overflowCount,
13531
- overflowSidecarName: paginated.sidecarPath ? import_path8.default.basename(paginated.sidecarPath) : void 0
13581
+ overflowSidecarName: paginated.sidecarPath ? import_path7.default.basename(paginated.sidecarPath) + (options.pdfPassword ? ".enc" : "") : void 0
13532
13582
  };
13533
13583
  for (const f of renderData.failures) f.screenshotBase64 = void 0;
13534
13584
  if (options.includeScreenshots !== false) {
@@ -13553,7 +13603,7 @@ var PdfGenerator = class {
13553
13603
  await page.goto(fileUrl, { waitUntil: "domcontentloaded", timeout: 6e4 });
13554
13604
  const showCharts = source.kind === "builtin" ? resolveSections(source.id, options.sections).charts : true;
13555
13605
  await this.chartRenderer.waitForCharts(page, showCharts);
13556
- logger.info(`Generating PDF \u2192 ${import_path8.default.relative(process.cwd(), outputPath)}`);
13606
+ logger.info(`Generating PDF \u2192 ${import_path7.default.relative(process.cwd(), outputPath)}`);
13557
13607
  await page.pdf({
13558
13608
  path: outputPath,
13559
13609
  format: "A4",
@@ -13563,19 +13613,129 @@ var PdfGenerator = class {
13563
13613
  } finally {
13564
13614
  await this.htmlWriter.cleanup();
13565
13615
  }
13616
+ return { sidecarPath: paginated.overflowCount > 0 ? paginated.sidecarPath : void 0 };
13566
13617
  }
13567
13618
  };
13568
13619
  function fmtMb(bytes) {
13569
13620
  return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
13570
13621
  }
13571
13622
 
13623
+ // src/redact/Redactor.ts
13624
+ init_cjs_shims();
13625
+ var Redactor = class {
13626
+ constructor(opts) {
13627
+ /** Indexes of user patterns disabled after throwing (warned once each). */
13628
+ this.disabled = /* @__PURE__ */ new Set();
13629
+ this.maskRaw = opts.mask;
13630
+ this.maskReplacement = opts.mask.replace(/\$/g, "$$$$");
13631
+ this.useBuiltins = opts.builtins;
13632
+ this.userPatterns = opts.patterns.map((p) => new RegExp(p, "g"));
13633
+ }
13634
+ redactText(s) {
13635
+ if (!s) return s;
13636
+ let out = s;
13637
+ if (this.useBuiltins) out = this.applyBuiltins(out);
13638
+ for (let i = 0; i < this.userPatterns.length; i++) {
13639
+ if (this.disabled.has(i)) continue;
13640
+ try {
13641
+ out = out.replace(this.userPatterns[i], this.maskReplacement);
13642
+ } catch (err) {
13643
+ this.disabled.add(i);
13644
+ logger.warn(
13645
+ `redact: custom pattern #${i + 1} threw and is disabled for this run: ${err.message}`
13646
+ );
13647
+ }
13648
+ }
13649
+ return out;
13650
+ }
13651
+ applyBuiltins(s) {
13652
+ const M = this.maskRaw;
13653
+ const MR = this.maskReplacement;
13654
+ return s.replace(/(\b[a-z][\w+.-]{0,63}:\/\/[^/\s:@]{1,512}:)([^@\s/]{1,512})(?=@)/gi, `$1${MR}`).replace(
13655
+ /\b(?:(authorization\s*[:=]\s*basic\s+)([A-Za-z0-9._~+/=-]{8,})|((?:authorization\s*[:=]\s*)?bearer\s+)([A-Za-z0-9._~+/=-]{8,}))/gi,
13656
+ (fullMatch, basicPrefix, _basicToken, bearerPrefix, bearerToken) => {
13657
+ if (basicPrefix) return `${basicPrefix}${M}`;
13658
+ if (bearerToken && /[^a-z]/.test(bearerToken)) return `${bearerPrefix}${M}`;
13659
+ return fullMatch;
13660
+ }
13661
+ ).replace(/\beyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\b/g, MR).replace(
13662
+ /\b(?:AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{16,}|gho_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|sk-[A-Za-z0-9_-]{16,}|rzp_(?:live|test)_[A-Za-z0-9]{8,}|AIza[A-Za-z0-9_-]{30,})\b/g,
13663
+ MR
13664
+ ).replace(
13665
+ /\b((?:[A-Za-z0-9]{1,32}[_-]){0,8}(?:password|passwd|passcode|pwd|secret|token|api[_-]?key|apikey|auth|authorization|credential|private[_-]?key|access[_-]?key|client[_-]?secret))\b(["']?\s*[=:]\s*)(?!(?:bearer|basic)\b)(?:(["'])((?:(?!\3).){1,512})\3|([^\s"',;&]{1,512}))/gi,
13666
+ (_m, key, sep, q, _quotedVal, _plainVal) => q ? `${key}${sep}${q}${M}${q}` : `${key}${sep}${M}`
13667
+ ).replace(
13668
+ /\b(fill|type|press)\b([^\n]{0,80}?\b(?:password|secret|token|pass|passcode|pin|otp)\b[^\n]{0,80}?\s)(["'])((?:(?!\3).)+)\3/gi,
13669
+ (_m, action, mid, q) => `${action}${mid}${q}${M}${q}`
13670
+ ).replace(/\b([A-Za-z0-9._%+-])[A-Za-z0-9._%+-]{0,63}@([A-Za-z0-9.-]{1,255}\.[A-Za-z]{2,})\b/g, "$1***@$2").replace(/[A-Za-z0-9_+/=-]{20,}/g, (m, offset, str) => {
13671
+ const before = offset > 0 ? str[offset - 1] : " ";
13672
+ const after = offset + m.length < str.length ? str[offset + m.length] : " ";
13673
+ const beforeIsWord = /[A-Za-z0-9_]/.test(before);
13674
+ const afterIsWord = /[A-Za-z0-9_]/.test(after);
13675
+ if (!beforeIsWord && !afterIsWord && /\d/.test(m) && /[A-Za-z]/.test(m)) {
13676
+ return M;
13677
+ }
13678
+ return m;
13679
+ });
13680
+ }
13681
+ };
13682
+
13683
+ // src/redact/redact-report.ts
13684
+ init_cjs_shims();
13685
+ function redactReportData(data, r) {
13686
+ const rd = (s) => s === void 0 ? void 0 : r.redactText(s);
13687
+ for (const f of data.failures) {
13688
+ f.testTitle = r.redactText(f.testTitle);
13689
+ f.suitePath = f.suitePath.map((s) => r.redactText(s));
13690
+ f.error.message = r.redactText(f.error.message);
13691
+ f.error.stack = rd(f.error.stack);
13692
+ if (f.execution) {
13693
+ f.execution.steps?.forEach((s) => redactStep(s, r));
13694
+ if (f.execution.failingStep) redactStep(f.execution.failingStep, r);
13695
+ if (f.execution.console) {
13696
+ f.execution.console.stdout = f.execution.console.stdout.map((l) => r.redactText(l));
13697
+ f.execution.console.stderr = f.execution.console.stderr.map((l) => r.redactText(l));
13698
+ }
13699
+ }
13700
+ }
13701
+ for (const p of data.projects) p.suites.forEach((s) => redactSuite(s, r));
13702
+ if (data.analysis) {
13703
+ for (const c of data.analysis.clusters) {
13704
+ c.representativeMessage = r.redactText(c.representativeMessage);
13705
+ c.tests = c.tests.map((t) => r.redactText(t));
13706
+ }
13707
+ data.analysis.oneLiner = r.redactText(data.analysis.oneLiner);
13708
+ }
13709
+ data.classifications?.forEach((c) => c.message = r.redactText(c.message));
13710
+ data.briefSentence = rd(data.briefSentence);
13711
+ data.charts.suiteResults.forEach((s) => s.label = r.redactText(s.label));
13712
+ data.charts.flakinessTable?.forEach((row) => row.title = r.redactText(row.title));
13713
+ data.environment.buildUrl = r.redactText(data.environment.buildUrl);
13714
+ }
13715
+ function redactStep(s, r) {
13716
+ s.title = r.redactText(s.title);
13717
+ if (s.errorMessage !== void 0) s.errorMessage = r.redactText(s.errorMessage);
13718
+ }
13719
+ function redactSuite(s, r) {
13720
+ s.title = r.redactText(s.title);
13721
+ for (const t of s.tests) {
13722
+ t.title = r.redactText(t.title);
13723
+ t.fullTitle = r.redactText(t.fullTitle);
13724
+ t.tags = t.tags.map((tag) => r.redactText(tag));
13725
+ t.annotations.forEach((a) => {
13726
+ if (a.description !== void 0) a.description = r.redactText(a.description);
13727
+ });
13728
+ }
13729
+ s.suites.forEach((child) => redactSuite(child, r));
13730
+ }
13731
+
13572
13732
  // src/reporter.ts
13573
- var import_path13 = __toESM(require("path"));
13733
+ var import_path12 = __toESM(require("path"));
13574
13734
 
13575
13735
  // src/history/HistoryManager.ts
13576
13736
  init_cjs_shims();
13577
13737
  var import_fs12 = __toESM(require("fs"));
13578
- var import_path9 = __toESM(require("path"));
13738
+ var import_path8 = __toESM(require("path"));
13579
13739
  var HistoryManager = class {
13580
13740
  constructor(filePath) {
13581
13741
  this.filePath = filePath;
@@ -13596,7 +13756,7 @@ var HistoryManager = class {
13596
13756
  }
13597
13757
  append(entry, maxSize) {
13598
13758
  try {
13599
- import_fs12.default.mkdirSync(import_path9.default.dirname(this.filePath), { recursive: true });
13759
+ import_fs12.default.mkdirSync(import_path8.default.dirname(this.filePath), { recursive: true });
13600
13760
  const next = {
13601
13761
  version: 1,
13602
13762
  entries: [entry, ...this.read()].slice(0, maxSize)
@@ -13694,14 +13854,14 @@ init_cjs_shims();
13694
13854
  // src/notify/NotificationSender.ts
13695
13855
  init_cjs_shims();
13696
13856
  var import_promises2 = require("fs/promises");
13697
- var import_path12 = require("path");
13857
+ var import_path11 = require("path");
13698
13858
 
13699
13859
  // src/notify/formatters/slack.ts
13700
13860
  init_cjs_shims();
13701
13861
 
13702
13862
  // src/notify/formatters/shared.ts
13703
13863
  init_cjs_shims();
13704
- var import_path10 = require("path");
13864
+ var import_path9 = require("path");
13705
13865
  function statusLabel(verdict) {
13706
13866
  switch (verdict) {
13707
13867
  case "passed":
@@ -13725,7 +13885,7 @@ function countsLine(stats) {
13725
13885
  return parts.join(" \xB7 ");
13726
13886
  }
13727
13887
  function reportName(pdfPaths, reportTitle) {
13728
- if (pdfPaths.length > 0) return (0, import_path10.basename)(pdfPaths[0]);
13888
+ if (pdfPaths.length > 0) return (0, import_path9.basename)(pdfPaths[0]);
13729
13889
  return reportTitle || "ReportForge";
13730
13890
  }
13731
13891
 
@@ -13825,7 +13985,7 @@ ${name}`,
13825
13985
  // src/notify/EmailSender.ts
13826
13986
  init_cjs_shims();
13827
13987
  var import_promises = require("fs/promises");
13828
- var import_path11 = require("path");
13988
+ var import_path10 = require("path");
13829
13989
 
13830
13990
  // src/notify/formatters/email.ts
13831
13991
  init_cjs_shims();
@@ -13899,7 +14059,7 @@ var EmailSender = class {
13899
14059
  const pdfPath = pdfPaths[0];
13900
14060
  try {
13901
14061
  const content = await (0, import_promises.readFile)(pdfPath);
13902
- body["attachments"] = [{ filename: (0, import_path11.basename)(pdfPath), content: content.toString("base64") }];
14062
+ body["attachments"] = [{ filename: (0, import_path10.basename)(pdfPath), content: content.toString("base64") }];
13903
14063
  } catch (err) {
13904
14064
  logger.warn(`[notify] email: could not read PDF for attachment: ${err instanceof Error ? err.message : String(err)}`);
13905
14065
  }
@@ -14025,7 +14185,7 @@ var NotificationSender = class {
14025
14185
  }
14026
14186
  const form = new FormData();
14027
14187
  form.append("payload_json", JSON.stringify(payload));
14028
- form.append("files[0]", new Blob([new Uint8Array(file)], { type: "application/pdf" }), (0, import_path12.basename)(pdfPath));
14188
+ form.append("files[0]", new Blob([new Uint8Array(file)], { type: "application/pdf" }), (0, import_path11.basename)(pdfPath));
14029
14189
  const controller = new AbortController();
14030
14190
  const timeoutId = setTimeout(() => controller.abort(), 3e4);
14031
14191
  try {
@@ -14099,6 +14259,10 @@ var LiveStreamer = class {
14099
14259
  }
14100
14260
  enqueueTest(update) {
14101
14261
  if (!this.enabled) return;
14262
+ if (this.config.redact) {
14263
+ if (update.title !== void 0) update.title = this.config.redact(update.title);
14264
+ if (update.currentStep !== void 0) update.currentStep = this.config.redact(update.currentStep);
14265
+ }
14102
14266
  const prevRetry = this.testStates.get(update.id)?.retry;
14103
14267
  this.testStates.set(update.id, { status: update.status, retry: update.retry ?? prevRetry });
14104
14268
  this.testBuffer.push(update);
@@ -14107,6 +14271,10 @@ var LiveStreamer = class {
14107
14271
  }
14108
14272
  enqueueEvent(event) {
14109
14273
  if (!this.enabled) return;
14274
+ if (this.config.redact) {
14275
+ if (event.title !== void 0) event.title = this.config.redact(event.title);
14276
+ if (event.text !== void 0) event.text = this.config.redact(event.text);
14277
+ }
14110
14278
  event.id = `${this.config.shardKey}:${this.eventSeq++}`;
14111
14279
  this.eventBuffer.push(event);
14112
14280
  this.capPreLicenseBuffer();
@@ -14413,7 +14581,10 @@ var PdfReporter = class {
14413
14581
  this.options = parseOptions(rawOptions);
14414
14582
  setLogLevel(this.options.logLevel);
14415
14583
  this.dataCollector = new DataCollector(this.options.capture);
14416
- this.version = true ? "0.22.0" : "0.x";
14584
+ if (this.options.redact.enabled) {
14585
+ this.redactor = new Redactor(this.options.redact);
14586
+ }
14587
+ this.version = true ? "0.23.0" : "0.x";
14417
14588
  logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
14418
14589
  this.licenseClient = new LicenseClient({
14419
14590
  licenseKey: this.options.licenseKey,
@@ -14504,6 +14675,9 @@ var PdfReporter = class {
14504
14675
  const reportData = this._buildReportData(collected, licenseInfo);
14505
14676
  runFailureAnalysis(reportData, this.options, this.cwd);
14506
14677
  reportData.briefSentence = buildBriefSentence(reportData.stats, reportData.analysis, reportData.charts.trend);
14678
+ if (this.redactor) {
14679
+ redactReportData(reportData, this.redactor);
14680
+ }
14507
14681
  let pdfPaths = [];
14508
14682
  try {
14509
14683
  pdfPaths = await this.pdfGenerator.generateAll(reportData, this.options, licenseInfo);
@@ -14535,7 +14709,10 @@ var PdfReporter = class {
14535
14709
  const historyPath = resolveHistoryPath(this.options, this.cwd);
14536
14710
  const hm = new HistoryManager(historyPath);
14537
14711
  const suffix = Math.random().toString(36).padEnd(8, "0").slice(2, 6);
14538
- const flakyTests = collectFlakyTests(collected.projects);
14712
+ const redactor = this.redactor;
14713
+ const flakyTests = collectFlakyTests(collected.projects).map(
14714
+ (t) => redactor ? { ...t, title: redactor.redactText(t.title) } : t
14715
+ );
14539
14716
  const entry = {
14540
14717
  runId: `${(/* @__PURE__ */ new Date()).toISOString()}-${suffix}`,
14541
14718
  timestamp: Date.now(),
@@ -14675,6 +14852,7 @@ var PdfReporter = class {
14675
14852
  const token = computeWatchToken(runId, this.options.licenseKey);
14676
14853
  const watchUrl = token ? buildWatchUrl(serverUrl, runId, token) : void 0;
14677
14854
  const env = detectCiEnv();
14855
+ if (this.redactor && env?.buildUrl) env.buildUrl = this.redactor.redactText(env.buildUrl);
14678
14856
  if (watchUrl) {
14679
14857
  void this.announceLive(watchUrl, env, shard).catch(() => {
14680
14858
  });
@@ -14689,6 +14867,7 @@ var PdfReporter = class {
14689
14867
  shardKey: this.liveShardKey,
14690
14868
  shardTotal: shard?.total ?? null,
14691
14869
  license: () => this.license(),
14870
+ redact: this.redactor ? (s) => this.redactor.redactText(s) : void 0,
14692
14871
  env,
14693
14872
  flushMs: live.flushMs
14694
14873
  });
@@ -14730,7 +14909,7 @@ var PdfReporter = class {
14730
14909
  }
14731
14910
  resolveProjectName() {
14732
14911
  try {
14733
- const pkgPath = import_path13.default.resolve(process.cwd(), "package.json");
14912
+ const pkgPath = import_path12.default.resolve(process.cwd(), "package.json");
14734
14913
  const pkg = require(pkgPath);
14735
14914
  return pkg.name ?? "Playwright Tests";
14736
14915
  } catch {
@@ -14780,9 +14959,9 @@ function deriveVerdict(stats) {
14780
14959
  }
14781
14960
  function resolveHistoryPath(options, cwd) {
14782
14961
  if (options.historyFile) {
14783
- return import_path13.default.isAbsolute(options.historyFile) ? options.historyFile : import_path13.default.resolve(cwd, options.historyFile);
14962
+ return import_path12.default.isAbsolute(options.historyFile) ? options.historyFile : import_path12.default.resolve(cwd, options.historyFile);
14784
14963
  }
14785
- return import_path13.default.join(resolveProjectDir(options, cwd), "history.json");
14964
+ return import_path12.default.join(resolveProjectDir(options, cwd), "history.json");
14786
14965
  }
14787
14966
  function flattenSuiteNodes(suites) {
14788
14967
  return suites.flatMap((s) => [s, ...flattenSuiteNodes(s.suites)]);