@prodgator/report 3.19.0 → 3.25.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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.js +988 -226
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -239,10 +239,10 @@ var require_utils = __commonJS({
239
239
  REGEX_SPECIAL_CHARS_GLOBAL
240
240
  } = require_constants();
241
241
  exports2.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
242
- exports2.hasRegexChars = (str) => REGEX_SPECIAL_CHARS.test(str);
243
- exports2.isRegexChar = (str) => str.length === 1 && exports2.hasRegexChars(str);
244
- exports2.escapeRegex = (str) => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
245
- exports2.toPosixSlashes = (str) => str.replace(REGEX_BACKSLASH, "/");
242
+ exports2.hasRegexChars = (str2) => REGEX_SPECIAL_CHARS.test(str2);
243
+ exports2.isRegexChar = (str2) => str2.length === 1 && exports2.hasRegexChars(str2);
244
+ exports2.escapeRegex = (str2) => str2.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
245
+ exports2.toPosixSlashes = (str2) => str2.replace(REGEX_BACKSLASH, "/");
246
246
  exports2.isWindows = () => {
247
247
  if (typeof navigator !== "undefined" && navigator.platform) {
248
248
  const platform = navigator.platform.toLowerCase();
@@ -253,8 +253,8 @@ var require_utils = __commonJS({
253
253
  }
254
254
  return false;
255
255
  };
256
- exports2.removeBackslashes = (str) => {
257
- return str.replace(REGEX_REMOVE_BACKSLASH, (match) => {
256
+ exports2.removeBackslashes = (str2) => {
257
+ return str2.replace(REGEX_REMOVE_BACKSLASH, (match) => {
258
258
  return match === "\\" ? "" : match;
259
259
  });
260
260
  };
@@ -344,7 +344,7 @@ var require_scan = __commonJS({
344
344
  const slashes = [];
345
345
  const tokens = [];
346
346
  const parts = [];
347
- let str = input;
347
+ let str2 = input;
348
348
  let index = -1;
349
349
  let start = 0;
350
350
  let lastIndex = 0;
@@ -363,10 +363,10 @@ var require_scan = __commonJS({
363
363
  let code;
364
364
  let token = { value: "", depth: 0, isGlob: false };
365
365
  const eos = () => index >= length;
366
- const peek = () => str.charCodeAt(index + 1);
366
+ const peek = () => str2.charCodeAt(index + 1);
367
367
  const advance = () => {
368
368
  prev = code;
369
- return str.charCodeAt(++index);
369
+ return str2.charCodeAt(++index);
370
370
  };
371
371
  while (index < length) {
372
372
  code = advance();
@@ -543,24 +543,24 @@ var require_scan = __commonJS({
543
543
  isExtglob = false;
544
544
  isGlob = false;
545
545
  }
546
- let base = str;
546
+ let base = str2;
547
547
  let prefix = "";
548
548
  let glob2 = "";
549
549
  if (start > 0) {
550
- prefix = str.slice(0, start);
551
- str = str.slice(start);
550
+ prefix = str2.slice(0, start);
551
+ str2 = str2.slice(start);
552
552
  lastIndex -= start;
553
553
  }
554
554
  if (base && isGlob === true && lastIndex > 0) {
555
- base = str.slice(0, lastIndex);
556
- glob2 = str.slice(lastIndex);
555
+ base = str2.slice(0, lastIndex);
556
+ glob2 = str2.slice(lastIndex);
557
557
  } else if (isGlob === true) {
558
558
  base = "";
559
- glob2 = str;
559
+ glob2 = str2;
560
560
  } else {
561
- base = str;
561
+ base = str2;
562
562
  }
563
- if (base && base !== "" && base !== "/" && base !== str) {
563
+ if (base && base !== "" && base !== "/" && base !== str2) {
564
564
  if (isPathSeparator(base.charCodeAt(base.length - 1))) {
565
565
  base = base.slice(0, -1);
566
566
  }
@@ -634,7 +634,7 @@ var require_scan = __commonJS({
634
634
  var require_parse = __commonJS({
635
635
  "node_modules/picomatch/lib/parse.js"(exports2, module2) {
636
636
  "use strict";
637
- var constants = require_constants();
637
+ var constants2 = require_constants();
638
638
  var utils = require_utils();
639
639
  var {
640
640
  MAX_LENGTH,
@@ -642,7 +642,7 @@ var require_parse = __commonJS({
642
642
  REGEX_NON_SPECIAL_CHARS,
643
643
  REGEX_SPECIAL_CHARS_BACKREF,
644
644
  REPLACEMENTS
645
- } = constants;
645
+ } = constants2;
646
646
  var expandRange = (args, options) => {
647
647
  if (typeof options.expandRange === "function") {
648
648
  return options.expandRange(...args, options);
@@ -851,7 +851,7 @@ var require_parse = __commonJS({
851
851
  if (options.maxExtglobRecursion === false) {
852
852
  return { risky: false };
853
853
  }
854
- const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion : constants.DEFAULT_MAX_EXTGLOB_RECURSION;
854
+ const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion : constants2.DEFAULT_MAX_EXTGLOB_RECURSION;
855
855
  const branches = splitTopLevel(body).map((branch) => branch.trim());
856
856
  if (branches.length > 1) {
857
857
  if (branches.some((branch) => branch === "") || branches.some((branch) => /^[*?]+$/.test(branch)) || hasRepeatedCharPrefixOverlap(branches)) {
@@ -897,8 +897,8 @@ var require_parse = __commonJS({
897
897
  const bos = { type: "bos", value: "", output: opts.prepend || "" };
898
898
  const tokens = [bos];
899
899
  const capture = opts.capture ? "" : "?:";
900
- const PLATFORM_CHARS = constants.globChars(opts.windows);
901
- const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
900
+ const PLATFORM_CHARS = constants2.globChars(opts.windows);
901
+ const EXTGLOB_CHARS = constants2.extglobChars(PLATFORM_CHARS);
902
902
  const {
903
903
  DOT_LITERAL,
904
904
  PLUS_LITERAL,
@@ -1275,15 +1275,15 @@ var require_parse = __commonJS({
1275
1275
  }
1276
1276
  let output = ")";
1277
1277
  if (brace.dots === true) {
1278
- const arr = tokens.slice();
1278
+ const arr2 = tokens.slice();
1279
1279
  const range = [];
1280
- for (let i = arr.length - 1; i >= 0; i--) {
1280
+ for (let i = arr2.length - 1; i >= 0; i--) {
1281
1281
  tokens.pop();
1282
- if (arr[i].type === "brace") {
1282
+ if (arr2[i].type === "brace") {
1283
1283
  break;
1284
1284
  }
1285
- if (arr[i].type !== "dots") {
1286
- range.unshift(arr[i].value);
1285
+ if (arr2[i].type !== "dots") {
1286
+ range.unshift(arr2[i].value);
1287
1287
  }
1288
1288
  }
1289
1289
  output = expandRange(range, opts);
@@ -1594,7 +1594,7 @@ var require_parse = __commonJS({
1594
1594
  NO_DOTS_SLASH,
1595
1595
  STAR,
1596
1596
  START_ANCHOR
1597
- } = constants.globChars(opts.windows);
1597
+ } = constants2.globChars(opts.windows);
1598
1598
  const nodot = opts.dot ? NO_DOTS : NO_DOT;
1599
1599
  const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
1600
1600
  const capture = opts.capture ? "" : "?:";
@@ -1607,8 +1607,8 @@ var require_parse = __commonJS({
1607
1607
  if (opts2.noglobstar === true) return star;
1608
1608
  return `(${capture}(?:(?!${START_ANCHOR}${opts2.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
1609
1609
  };
1610
- const create = (str) => {
1611
- switch (str) {
1610
+ const create = (str2) => {
1611
+ switch (str2) {
1612
1612
  case "*":
1613
1613
  return `${nodot}${ONE_CHAR}${star}`;
1614
1614
  case ".*":
@@ -1626,7 +1626,7 @@ var require_parse = __commonJS({
1626
1626
  case "**/.*":
1627
1627
  return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
1628
1628
  default: {
1629
- const match = /^(.*?)\.(\w+)$/.exec(str);
1629
+ const match = /^(.*?)\.(\w+)$/.exec(str2);
1630
1630
  if (!match) return;
1631
1631
  const source2 = create(match[1]);
1632
1632
  if (!source2) return;
@@ -1652,14 +1652,14 @@ var require_picomatch = __commonJS({
1652
1652
  var scan = require_scan();
1653
1653
  var parse = require_parse();
1654
1654
  var utils = require_utils();
1655
- var constants = require_constants();
1655
+ var constants2 = require_constants();
1656
1656
  var isObject = (val) => val && typeof val === "object" && !Array.isArray(val);
1657
1657
  var picomatch = (glob2, options, returnState = false) => {
1658
1658
  if (Array.isArray(glob2)) {
1659
1659
  const fns = glob2.map((input) => picomatch(input, options, returnState));
1660
- const arrayMatcher = (str) => {
1660
+ const arrayMatcher = (str2) => {
1661
1661
  for (const isMatch of fns) {
1662
- const state2 = isMatch(str);
1662
+ const state2 = isMatch(str2);
1663
1663
  if (state2) return state2;
1664
1664
  }
1665
1665
  return false;
@@ -1735,7 +1735,7 @@ var require_picomatch = __commonJS({
1735
1735
  const regex = glob2 instanceof RegExp ? glob2 : picomatch.makeRe(glob2, options);
1736
1736
  return regex.test(utils.basename(input, { windows: posix }));
1737
1737
  };
1738
- picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str);
1738
+ picomatch.isMatch = (str2, patterns, options) => picomatch(patterns, options)(str2);
1739
1739
  picomatch.parse = (pattern, options) => {
1740
1740
  if (Array.isArray(pattern)) return pattern.map((p) => picomatch.parse(p, options));
1741
1741
  return parse(pattern, { ...options, fastpaths: false });
@@ -1780,7 +1780,7 @@ var require_picomatch = __commonJS({
1780
1780
  return /$^/;
1781
1781
  }
1782
1782
  };
1783
- picomatch.constants = constants;
1783
+ picomatch.constants = constants2;
1784
1784
  module2.exports = picomatch;
1785
1785
  }
1786
1786
  });
@@ -1826,7 +1826,7 @@ var require_dist = __commonJS({
1826
1826
  enumerable: true
1827
1827
  }) : target, mod));
1828
1828
  var path = __toESM2(require("path"));
1829
- var fs5 = __toESM2(require("fs"));
1829
+ var fs3 = __toESM2(require("fs"));
1830
1830
  function cleanPath(path$1) {
1831
1831
  let normalized = (0, path.normalize)(path$1);
1832
1832
  if (normalized.length > 1 && normalized[normalized.length - 1] === path.sep) normalized = normalized.substring(0, normalized.length - 1);
@@ -2123,7 +2123,7 @@ var require_dist = __commonJS({
2123
2123
  symlinks: /* @__PURE__ */ new Map(),
2124
2124
  visited: [""].slice(0, 0),
2125
2125
  controller: new Aborter(),
2126
- fs: options.fs || fs5
2126
+ fs: options.fs || fs3
2127
2127
  };
2128
2128
  this.joinPath = build$7(this.root, options);
2129
2129
  this.pushDirectory = build$6(this.root, options);
@@ -2358,7 +2358,7 @@ var require_dist2 = __commonJS({
2358
2358
  value: mod,
2359
2359
  enumerable: true
2360
2360
  }) : target, mod));
2361
- var fs5 = require("fs");
2361
+ var fs3 = require("fs");
2362
2362
  var path = require("path");
2363
2363
  var url = require("url");
2364
2364
  var fdir = require_dist();
@@ -2607,12 +2607,12 @@ var require_dist2 = __commonJS({
2607
2607
  opts.cwd = (opts.cwd instanceof URL ? (0, url.fileURLToPath)(opts.cwd) : (0, path.resolve)(opts.cwd || process.cwd())).replace(BACKSLASHES, "/");
2608
2608
  opts.ignore = ensureStringArray(opts.ignore);
2609
2609
  opts.fs && (opts.fs = {
2610
- readdir: opts.fs.readdir || fs5.readdir,
2611
- readdirSync: opts.fs.readdirSync || fs5.readdirSync,
2612
- realpath: opts.fs.realpath || fs5.realpath,
2613
- realpathSync: opts.fs.realpathSync || fs5.realpathSync,
2614
- stat: opts.fs.stat || fs5.stat,
2615
- statSync: opts.fs.statSync || fs5.statSync
2610
+ readdir: opts.fs.readdir || fs3.readdir,
2611
+ readdirSync: opts.fs.readdirSync || fs3.readdirSync,
2612
+ realpath: opts.fs.realpath || fs3.realpath,
2613
+ realpathSync: opts.fs.realpathSync || fs3.realpathSync,
2614
+ stat: opts.fs.stat || fs3.stat,
2615
+ statSync: opts.fs.statSync || fs3.statSync
2616
2616
  });
2617
2617
  if (opts.debug) log("globbing with options:", opts);
2618
2618
  return opts;
@@ -2641,10 +2641,6 @@ var require_dist2 = __commonJS({
2641
2641
  }
2642
2642
  });
2643
2643
 
2644
- // src/cli/main.ts
2645
- var import_fs5 = require("fs");
2646
- var import_path4 = require("path");
2647
-
2648
2644
  // src/core/text.ts
2649
2645
  var MAX_SUMMARY_BYTES = 1024 * 1024;
2650
2646
  var MARKER = "\n\n\u2026(truncated by prodgator-report)";
@@ -2656,9 +2652,6 @@ function truncateUtf8(text, maxBytes) {
2656
2652
  return { text: bytes.subarray(0, end).toString("utf8") + MARKER, truncated: true };
2657
2653
  }
2658
2654
 
2659
- // src/core/junit.ts
2660
- var import_fs = require("fs");
2661
-
2662
2655
  // node_modules/fast-xml-parser/src/util.js
2663
2656
  var nameStartChar = ":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
2664
2657
  var nameChar = nameStartChar + "\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
@@ -3348,29 +3341,29 @@ var EntityDecoder = class {
3348
3341
  * @param {string} str
3349
3342
  * @returns {string}
3350
3343
  */
3351
- decode(str) {
3352
- if (typeof str !== "string" || str.length === 0) return str;
3353
- if (str.indexOf("&") === -1) return str;
3354
- const original = str;
3344
+ decode(str2) {
3345
+ if (typeof str2 !== "string" || str2.length === 0) return str2;
3346
+ if (str2.indexOf("&") === -1) return str2;
3347
+ const original = str2;
3355
3348
  const chunks = [];
3356
- const len = str.length;
3349
+ const len = str2.length;
3357
3350
  let last = 0;
3358
3351
  let i = 0;
3359
3352
  const limitExpansions = this._maxTotalExpansions > 0;
3360
3353
  const limitLength = this._maxExpandedLength > 0;
3361
3354
  const checkLimits = limitExpansions || limitLength;
3362
3355
  while (i < len) {
3363
- if (str.charCodeAt(i) !== 38) {
3356
+ if (str2.charCodeAt(i) !== 38) {
3364
3357
  i++;
3365
3358
  continue;
3366
3359
  }
3367
3360
  let j = i + 1;
3368
- while (j < len && str.charCodeAt(j) !== 59 && j - i <= 32) j++;
3369
- if (j >= len || str.charCodeAt(j) !== 59) {
3361
+ while (j < len && str2.charCodeAt(j) !== 59 && j - i <= 32) j++;
3362
+ if (j >= len || str2.charCodeAt(j) !== 59) {
3370
3363
  i++;
3371
3364
  continue;
3372
3365
  }
3373
- const token = str.slice(i + 1, j);
3366
+ const token = str2.slice(i + 1, j);
3374
3367
  if (token.length === 0) {
3375
3368
  i++;
3376
3369
  continue;
@@ -3402,7 +3395,7 @@ var EntityDecoder = class {
3402
3395
  i++;
3403
3396
  continue;
3404
3397
  }
3405
- if (i > last) chunks.push(str.slice(last, i));
3398
+ if (i > last) chunks.push(str2.slice(last, i));
3406
3399
  chunks.push(replacement);
3407
3400
  last = j + 1;
3408
3401
  i = last;
@@ -3428,8 +3421,8 @@ var EntityDecoder = class {
3428
3421
  }
3429
3422
  }
3430
3423
  }
3431
- if (last < len) chunks.push(str.slice(last));
3432
- const result = chunks.length === 0 ? str : chunks.join("");
3424
+ if (last < len) chunks.push(str2.slice(last));
3425
+ const result = chunks.length === 0 ? str2 : chunks.join("");
3433
3426
  return this._postCheck(result, original);
3434
3427
  }
3435
3428
  // -------------------------------------------------------------------------
@@ -3748,7 +3741,7 @@ var getRegexes = (xmlVersion = "1.0", asciiOnly = false) => {
3748
3741
  if (asciiOnly) return regexesAscii;
3749
3742
  return xmlVersion === "1.1" ? regexes11 : regexes10;
3750
3743
  };
3751
- var qName = (str, { xmlVersion = "1.0", asciiOnly = false } = {}) => getRegexes(xmlVersion, asciiOnly).qName.test(str);
3744
+ var qName = (str2, { xmlVersion = "1.0", asciiOnly = false } = {}) => getRegexes(xmlVersion, asciiOnly).qName.test(str2);
3752
3745
 
3753
3746
  // node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js
3754
3747
  var DocTypeReader = class {
@@ -4196,13 +4189,13 @@ var CHAR_0 = 48;
4196
4189
  var CHAR_9 = 57;
4197
4190
  var CHAR_MINUS = 45;
4198
4191
  var MINUS_SET = /* @__PURE__ */ new Set([8722, 65293, 65123]);
4199
- function anynum(str) {
4200
- if (typeof str !== "string") return str;
4201
- const len = str.length;
4202
- if (len === 0) return str;
4192
+ function anynum(str2) {
4193
+ if (typeof str2 !== "string") return str2;
4194
+ const len = str2.length;
4195
+ if (len === 0) return str2;
4203
4196
  let firstHit = -1;
4204
4197
  for (let i = 0; i < len; i++) {
4205
- const cc = str.charCodeAt(i);
4198
+ const cc = str2.charCodeAt(i);
4206
4199
  if (cc >= CHAR_0 && cc <= CHAR_9 || cc === CHAR_MINUS) continue;
4207
4200
  if (cc < TABLE_OFFSET) {
4208
4201
  if (MINUS_SET.has(cc)) {
@@ -4213,7 +4206,7 @@ function anynum(str) {
4213
4206
  }
4214
4207
  if (cc >= 55296 && cc <= 56319) {
4215
4208
  if (i + 1 < len) {
4216
- const low = str.charCodeAt(i + 1);
4209
+ const low = str2.charCodeAt(i + 1);
4217
4210
  if (low >= 56320 && low <= 57343) {
4218
4211
  const cp = 65536 + (cc - 55296 << 10) + (low - 56320);
4219
4212
  if (HIGH_MAP.has(cp)) {
@@ -4229,22 +4222,22 @@ function anynum(str) {
4229
4222
  break;
4230
4223
  }
4231
4224
  }
4232
- if (firstHit === -1) return str;
4225
+ if (firstHit === -1) return str2;
4233
4226
  const chars = [];
4234
- if (firstHit > 0) chars.push(str.slice(0, firstHit));
4227
+ if (firstHit > 0) chars.push(str2.slice(0, firstHit));
4235
4228
  for (let i = firstHit; i < len; i++) {
4236
- const cc = str.charCodeAt(i);
4229
+ const cc = str2.charCodeAt(i);
4237
4230
  if (cc >= CHAR_0 && cc <= CHAR_9 || cc === CHAR_MINUS) {
4238
- chars.push(str[i]);
4231
+ chars.push(str2[i]);
4239
4232
  continue;
4240
4233
  }
4241
4234
  if (cc < TABLE_OFFSET) {
4242
- chars.push(MINUS_SET.has(cc) ? "-" : str[i]);
4235
+ chars.push(MINUS_SET.has(cc) ? "-" : str2[i]);
4243
4236
  continue;
4244
4237
  }
4245
4238
  if (cc >= 55296 && cc <= 56319) {
4246
4239
  if (i + 1 < len) {
4247
- const low = str.charCodeAt(i + 1);
4240
+ const low = str2.charCodeAt(i + 1);
4248
4241
  if (low >= 56320 && low <= 57343) {
4249
4242
  const cp = 65536 + (cc - 55296 << 10) + (low - 56320);
4250
4243
  const d2 = HIGH_MAP.get(cp);
@@ -4255,7 +4248,7 @@ function anynum(str) {
4255
4248
  }
4256
4249
  }
4257
4250
  }
4258
- chars.push(str[i]);
4251
+ chars.push(str2[i]);
4259
4252
  continue;
4260
4253
  }
4261
4254
  if (MINUS_SET.has(cc)) {
@@ -4263,7 +4256,7 @@ function anynum(str) {
4263
4256
  continue;
4264
4257
  }
4265
4258
  const d = TABLE[cc - TABLE_OFFSET];
4266
- chars.push(d !== NOT_DIGIT ? String.fromCharCode(d + 48) : str[i]);
4259
+ chars.push(d !== NOT_DIGIT ? String.fromCharCode(d + 48) : str2[i]);
4267
4260
  }
4268
4261
  return chars.join("");
4269
4262
  }
@@ -4286,12 +4279,12 @@ var consider = {
4286
4279
  // "null", "infinity" (Infinity type), "string" ("Infinity" (the string literal))
4287
4280
  unicode: false
4288
4281
  };
4289
- function toNumber(str, options = {}) {
4282
+ function toNumber(str2, options = {}) {
4290
4283
  options = Object.assign({}, consider, options);
4291
- if (!str || typeof str !== "string") return str;
4292
- let trimmedStr = str.trim();
4293
- if (trimmedStr.length === 0) return str;
4294
- else if (options.skipLike !== void 0 && options.skipLike.test(trimmedStr)) return str;
4284
+ if (!str2 || typeof str2 !== "string") return str2;
4285
+ let trimmedStr = str2.trim();
4286
+ if (trimmedStr.length === 0) return str2;
4287
+ else if (options.skipLike !== void 0 && options.skipLike.test(trimmedStr)) return str2;
4295
4288
  else if (trimmedStr === "0") return 0;
4296
4289
  if (options.unicode) {
4297
4290
  trimmedStr = anynum_default(trimmedStr);
@@ -4304,9 +4297,9 @@ function toNumber(str, options = {}) {
4304
4297
  } else if (options.octal && octRegex.test(trimmedStr)) {
4305
4298
  return parse_int(trimmedStr, 8);
4306
4299
  } else if (!isFinite(trimmedStr)) {
4307
- return handleInfinity(str, Number(trimmedStr), options);
4300
+ return handleInfinity(str2, Number(trimmedStr), options);
4308
4301
  } else if (trimmedStr.includes("e") || trimmedStr.includes("E")) {
4309
- return resolveEnotation(str, trimmedStr, options);
4302
+ return resolveEnotation(str2, trimmedStr, options);
4310
4303
  } else {
4311
4304
  const match = numRegex.exec(trimmedStr);
4312
4305
  if (match) {
@@ -4315,38 +4308,38 @@ function toNumber(str, options = {}) {
4315
4308
  let numTrimmedByZeros = trimZeros(match[3]);
4316
4309
  const decimalAdjacentToLeadingZeros = sign ? (
4317
4310
  // 0., -00., 000.
4318
- str[leadingZeros.length + 1] === "."
4319
- ) : str[leadingZeros.length] === ".";
4311
+ str2[leadingZeros.length + 1] === "."
4312
+ ) : str2[leadingZeros.length] === ".";
4320
4313
  if (!options.leadingZeros && (leadingZeros.length > 1 || leadingZeros.length === 1 && !decimalAdjacentToLeadingZeros)) {
4321
- return str;
4314
+ return str2;
4322
4315
  } else {
4323
4316
  const num = Number(trimmedStr);
4324
4317
  const parsedStr = String(num);
4325
4318
  if (num === 0) return num;
4326
4319
  if (parsedStr.search(/[eE]/) !== -1) {
4327
4320
  if (options.eNotation) return num;
4328
- else return str;
4321
+ else return str2;
4329
4322
  } else if (trimmedStr.indexOf(".") !== -1) {
4330
4323
  if (parsedStr === "0") return num;
4331
4324
  else if (parsedStr === numTrimmedByZeros) return num;
4332
4325
  else if (parsedStr === `${sign}${numTrimmedByZeros}`) return num;
4333
- else return str;
4326
+ else return str2;
4334
4327
  }
4335
4328
  let n = leadingZeros ? numTrimmedByZeros : trimmedStr;
4336
4329
  if (leadingZeros) {
4337
- return n === parsedStr || sign + n === parsedStr ? num : str;
4330
+ return n === parsedStr || sign + n === parsedStr ? num : str2;
4338
4331
  } else {
4339
- return n === parsedStr || n === sign + parsedStr ? num : str;
4332
+ return n === parsedStr || n === sign + parsedStr ? num : str2;
4340
4333
  }
4341
4334
  }
4342
4335
  } else {
4343
- return str;
4336
+ return str2;
4344
4337
  }
4345
4338
  }
4346
4339
  }
4347
4340
  var eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/;
4348
- function resolveEnotation(str, trimmedStr, options) {
4349
- if (!options.eNotation) return str;
4341
+ function resolveEnotation(str2, trimmedStr, options) {
4342
+ if (!options.eNotation) return str2;
4350
4343
  const notation = trimmedStr.match(eNotationRegx);
4351
4344
  if (notation) {
4352
4345
  let sign = notation[1] || "";
@@ -4354,21 +4347,21 @@ function resolveEnotation(str, trimmedStr, options) {
4354
4347
  const leadingZeros = notation[2];
4355
4348
  const eAdjacentToLeadingZeros = sign ? (
4356
4349
  // 0E.
4357
- str[leadingZeros.length + 1] === eChar
4358
- ) : str[leadingZeros.length] === eChar;
4359
- if (leadingZeros.length > 1 && eAdjacentToLeadingZeros) return str;
4350
+ str2[leadingZeros.length + 1] === eChar
4351
+ ) : str2[leadingZeros.length] === eChar;
4352
+ if (leadingZeros.length > 1 && eAdjacentToLeadingZeros) return str2;
4360
4353
  else if (leadingZeros.length === 1 && (notation[3].startsWith(`.${eChar}`) || notation[3][0] === eChar)) {
4361
4354
  return Number(trimmedStr);
4362
4355
  } else if (leadingZeros.length > 0) {
4363
4356
  if (options.leadingZeros && !eAdjacentToLeadingZeros) {
4364
4357
  trimmedStr = (notation[1] || "") + notation[3];
4365
4358
  return Number(trimmedStr);
4366
- } else return str;
4359
+ } else return str2;
4367
4360
  } else {
4368
4361
  return Number(trimmedStr);
4369
4362
  }
4370
4363
  } else {
4371
- return str;
4364
+ return str2;
4372
4365
  }
4373
4366
  }
4374
4367
  function trimZeros(numStr) {
@@ -4384,14 +4377,14 @@ function trimZeros(numStr) {
4384
4377
  return numStr;
4385
4378
  }
4386
4379
  function parse_int(numStr, base) {
4387
- const str = numStr.trim();
4388
- if (base === 2 || base === 8) numStr = str.substring(2);
4380
+ const str2 = numStr.trim();
4381
+ if (base === 2 || base === 8) numStr = str2.substring(2);
4389
4382
  if (parseInt) return parseInt(numStr, base);
4390
4383
  else if (Number.parseInt) return Number.parseInt(numStr, base);
4391
4384
  else if (window && window.parseInt) return window.parseInt(numStr, base);
4392
4385
  else throw new Error("parseInt, Number.parseInt, window.parseInt are not supported");
4393
4386
  }
4394
- function handleInfinity(str, num, options) {
4387
+ function handleInfinity(str2, num, options) {
4395
4388
  const isPositive = num === Infinity;
4396
4389
  switch (options.infinity.toLowerCase()) {
4397
4390
  case "null":
@@ -4403,7 +4396,7 @@ function handleInfinity(str, num, options) {
4403
4396
  return isPositive ? "Infinity" : "-Infinity";
4404
4397
  case "original":
4405
4398
  default:
4406
- return str;
4399
+ return str2;
4407
4400
  }
4408
4401
  }
4409
4402
 
@@ -6485,12 +6478,12 @@ function tagExpWithClosingIndex(xmlData, i, closingChar = ">") {
6485
6478
  }
6486
6479
  }
6487
6480
  }
6488
- function findClosingIndex(xmlData, str, i, errMsg) {
6489
- const closingIndex = xmlData.indexOf(str, i);
6481
+ function findClosingIndex(xmlData, str2, i, errMsg) {
6482
+ const closingIndex = xmlData.indexOf(str2, i);
6490
6483
  if (closingIndex === -1) {
6491
6484
  throw new Error(errMsg);
6492
6485
  } else {
6493
- return closingIndex + str.length - 1;
6486
+ return closingIndex + str2.length - 1;
6494
6487
  }
6495
6488
  }
6496
6489
  function findClosingChar(xmlData, char, i, errMsg) {
@@ -6621,11 +6614,11 @@ function stripAttributePrefix(attrs, prefix) {
6621
6614
  function prettify(node, options, matcher, readonlyMatcher) {
6622
6615
  return compress(node, options, matcher, readonlyMatcher);
6623
6616
  }
6624
- function compress(arr, options, matcher, readonlyMatcher) {
6617
+ function compress(arr2, options, matcher, readonlyMatcher) {
6625
6618
  let text;
6626
6619
  const compressedObj = {};
6627
- for (let i = 0; i < arr.length; i++) {
6628
- const tagObj = arr[i];
6620
+ for (let i = 0; i < arr2.length; i++) {
6621
+ const tagObj = arr2[i];
6629
6622
  const property = propName(tagObj);
6630
6623
  if (property !== void 0 && property !== options.textNodeName) {
6631
6624
  const rawAttrs = stripAttributePrefix(
@@ -6778,6 +6771,103 @@ var XMLParser = class {
6778
6771
  }
6779
6772
  };
6780
6773
 
6774
+ // src/core/safe-path.ts
6775
+ var import_crypto = require("crypto");
6776
+ var import_fs = require("fs");
6777
+ var import_path = require("path");
6778
+ var O_NOFOLLOW = import_fs.constants.O_NOFOLLOW ?? 0;
6779
+ var outside = (rel) => !rel || rel === ".." || rel.startsWith(`..${import_path.sep}`) || rel.startsWith("../") || (0, import_path.isAbsolute)(rel) || /^[A-Za-z]:/.test(rel);
6780
+ var underGit = (rel) => rel.split(/[\\/]/).includes(".git");
6781
+ var refuse = (reason, message) => ({ ok: false, reason, message });
6782
+ function displayName(root, file) {
6783
+ const rel = (0, import_path.relative)((0, import_path.resolve)(root), (0, import_path.resolve)(root, file));
6784
+ return outside(rel) ? file : rel.split(import_path.sep).join("/");
6785
+ }
6786
+ async function checkWorkspacePath(root, name, opts = {}) {
6787
+ const display = opts.display ?? name;
6788
+ const absolute = (0, import_path.resolve)(root, name);
6789
+ const rel = (0, import_path.relative)((0, import_path.resolve)(root), absolute);
6790
+ if (outside(rel)) return refuse("outside", `${display} is outside the workspace`);
6791
+ const path = rel.split(import_path.sep).join("/");
6792
+ if (underGit(path)) return refuse("git", `${display} is inside a .git directory`);
6793
+ let real;
6794
+ let realRoot;
6795
+ try {
6796
+ [real, realRoot] = await Promise.all([import_fs.promises.realpath(absolute), import_fs.promises.realpath(root)]);
6797
+ } catch {
6798
+ return refuse("missing", `${display} was not found`);
6799
+ }
6800
+ const realRel = (0, import_path.relative)(realRoot, real);
6801
+ if (outside(realRel)) return refuse("symlink", `${display} resolves outside the workspace (symlink)`);
6802
+ if (underGit(realRel)) return refuse("git", `${display} resolves into a .git directory (symlink)`);
6803
+ if (opts.followLinks === false && realRel !== rel) return refuse("symlink", `${display} is a symlink`);
6804
+ return { ok: true, file: { absolute, path, real } };
6805
+ }
6806
+ async function openWorkspaceFile(root, name, opts = {}) {
6807
+ const display = opts.display ?? name;
6808
+ const checked = await checkWorkspacePath(root, name, opts);
6809
+ if (!checked.ok) return checked;
6810
+ const { file } = checked;
6811
+ let handle;
6812
+ try {
6813
+ handle = await import_fs.promises.open(file.real, import_fs.constants.O_RDONLY | O_NOFOLLOW);
6814
+ } catch (e) {
6815
+ const code = e.code;
6816
+ return code === "ENOENT" ? refuse("missing", `${display} was not found`) : refuse("changed", `${display} could not be opened safely`);
6817
+ }
6818
+ try {
6819
+ const st = await handle.stat();
6820
+ if (!st.isFile()) {
6821
+ await handle.close();
6822
+ return refuse("not-file", `${display} is not a file`);
6823
+ }
6824
+ if (!await sameFile(handle, st, file.real, await import_fs.promises.realpath(root))) {
6825
+ await handle.close();
6826
+ return refuse("changed", `${display} changed before it could be read (symlink)`);
6827
+ }
6828
+ return { ok: true, file, size: st.size, handle };
6829
+ } catch {
6830
+ await handle.close().catch(() => {
6831
+ });
6832
+ return refuse("changed", `${display} changed before it could be read (symlink)`);
6833
+ }
6834
+ }
6835
+ async function sameFile(handle, st, real, realRoot) {
6836
+ const viaProc = await import_fs.promises.realpath(`/proc/self/fd/${handle.fd}`).catch(() => null);
6837
+ if (viaProc !== null) {
6838
+ const rel = (0, import_path.relative)(realRoot, viaProc);
6839
+ return viaProc === real && !outside(rel) && !underGit(rel);
6840
+ }
6841
+ const again = await import_fs.promises.realpath(real).catch(() => null);
6842
+ if (again !== real) return false;
6843
+ const now = await import_fs.promises.stat(real).catch(() => null);
6844
+ return !!now && now.dev === st.dev && now.ino === st.ino;
6845
+ }
6846
+ async function readWorkspaceText(root, name, opts = {}) {
6847
+ const opened = await openWorkspaceFile(root, name, opts);
6848
+ if (!opened.ok) return opened;
6849
+ try {
6850
+ if (opts.maxBytes !== void 0 && opened.size > opts.maxBytes) return { ok: true, file: opened.file, size: opened.size, text: null };
6851
+ return { ok: true, file: opened.file, size: opened.size, text: await opened.handle.readFile("utf8") };
6852
+ } finally {
6853
+ await opened.handle.close();
6854
+ }
6855
+ }
6856
+ async function sha256Handle(handle) {
6857
+ const hash = (0, import_crypto.createHash)("sha256");
6858
+ for await (const chunk of handle.createReadStream({ autoClose: false, start: 0 })) hash.update(chunk);
6859
+ return hash.digest("hex");
6860
+ }
6861
+ async function hashWorkspaceFile(root, name, opts = {}) {
6862
+ const opened = await openWorkspaceFile(root, name, opts);
6863
+ if (!opened.ok) return opened;
6864
+ try {
6865
+ return { ok: true, file: opened.file, size: opened.size, sha256: await sha256Handle(opened.handle) };
6866
+ } finally {
6867
+ await opened.handle.close();
6868
+ }
6869
+ }
6870
+
6781
6871
  // src/core/junit.ts
6782
6872
  var MAX_FILE_BYTES = 50 * 1024 * 1024;
6783
6873
  var MAX_FILES = 500;
@@ -6822,12 +6912,17 @@ async function junitAttestation(patterns, opts) {
6822
6912
  if (!patterns.length) return null;
6823
6913
  const find = opts.glob;
6824
6914
  const files = (await find(patterns)).slice(0, MAX_FILES);
6825
- if (!files.length) return null;
6826
6915
  const total = empty();
6916
+ let read = 0;
6827
6917
  for (const f of files) {
6828
- const stat = await import_fs.promises.stat(f);
6829
- if (stat.size > MAX_FILE_BYTES) continue;
6830
- const t = parseJUnitXml(await import_fs.promises.readFile(f, "utf8"));
6918
+ const r = await readWorkspaceText(opts.workspace, f, { maxBytes: MAX_FILE_BYTES, display: displayName(opts.workspace, f) });
6919
+ if (!r.ok) {
6920
+ if (r.reason !== "missing") opts.warn(`${r.message} and was not read`);
6921
+ continue;
6922
+ }
6923
+ read++;
6924
+ if (r.text === null) continue;
6925
+ const t = parseJUnitXml(r.text);
6831
6926
  total.passed += t.passed;
6832
6927
  total.failed += t.failed;
6833
6928
  total.skipped += t.skipped;
@@ -6835,87 +6930,582 @@ async function junitAttestation(patterns, opts) {
6835
6930
  total.durationSeconds = (total.durationSeconds ?? 0) + (t.durationSeconds ?? 0);
6836
6931
  total.failures.push(...t.failures.slice(0, 50 - total.failures.length));
6837
6932
  }
6933
+ if (!read) return null;
6838
6934
  return { kind: "test-results", name: opts.name ?? "junit", data: { ...total } };
6839
6935
  }
6840
6936
 
6841
6937
  // src/core/attestations.ts
6938
+ var import_crypto3 = require("crypto");
6842
6939
  var import_fs3 = require("fs");
6843
- var import_path2 = require("path");
6940
+ var import_os = require("os");
6941
+ var import_path3 = require("path");
6844
6942
 
6845
- // src/core/artifacts.ts
6846
- var import_crypto = require("crypto");
6943
+ // src/core/blame.ts
6944
+ var import_child_process = require("child_process");
6945
+ var import_crypto2 = require("crypto");
6847
6946
  var import_fs2 = require("fs");
6848
- var import_path = require("path");
6849
- var MAX_ARTIFACT_FILES = 50;
6850
- var TYPES = {
6851
- ".zip": "application/zip",
6852
- ".gz": "application/gzip",
6853
- ".tgz": "application/gzip",
6854
- ".tar": "application/x-tar",
6855
- ".json": "application/json",
6856
- ".xml": "application/xml",
6857
- ".pdf": "application/pdf",
6858
- ".txt": "text/plain",
6859
- ".log": "text/plain",
6860
- ".info": "text/plain",
6861
- ".md": "text/markdown",
6862
- ".csv": "text/csv",
6863
- ".html": "text/html",
6864
- ".htm": "text/html",
6865
- ".png": "image/png",
6866
- ".jpg": "image/jpeg",
6867
- ".jpeg": "image/jpeg",
6868
- ".svg": "image/svg+xml"
6947
+ var import_path2 = require("path");
6948
+ var import_url = require("url");
6949
+ function createBlameBudget(limits = {}, now = Date.now()) {
6950
+ return {
6951
+ deadline: now + (limits.maxMs ?? 6e4),
6952
+ resultsLeft: limits.maxResults ?? 500,
6953
+ deepensLeft: limits.maxDeepen ?? 3,
6954
+ gitMissing: false,
6955
+ snippetDeadline: null,
6956
+ snippetMs: limits.maxSnippetMs ?? 3e4,
6957
+ snippetsLeft: limits.maxSnippets ?? 5e3
6958
+ };
6959
+ }
6960
+ var BLAME_SIDECAR_SUFFIX = ".prodgator-blame.json";
6961
+ var MAX_RANGE_LINES = 50;
6962
+ var DEEPEN_BY = 200;
6963
+ var MAX_CALL_MS = 3e4;
6964
+ var MAX_RUNS = 20;
6965
+ var SKIP_KINDS = /* @__PURE__ */ new Set(["pass", "notApplicable", "informational"]);
6966
+ var GitError = class extends Error {
6967
+ constructor(message, stderr, code) {
6968
+ super(message);
6969
+ this.stderr = stderr;
6970
+ this.code = code;
6971
+ }
6869
6972
  };
6870
- var contentTypeFor = (path) => TYPES[(0, import_path.extname)(path).toLowerCase()] ?? "application/octet-stream";
6871
- function sha256File(path) {
6872
- return new Promise((resolveHash, reject) => {
6873
- const hash = (0, import_crypto.createHash)("sha256");
6874
- (0, import_fs2.createReadStream)(path).on("error", reject).on("data", (c) => hash.update(c)).on("end", () => resolveHash(hash.digest("hex")));
6973
+ var defaultIsExecutable = (platform) => (file) => {
6974
+ try {
6975
+ if (!(0, import_fs2.statSync)(file).isFile()) return false;
6976
+ if (platform !== "win32") (0, import_fs2.accessSync)(file, import_fs2.constants.X_OK);
6977
+ return true;
6978
+ } catch {
6979
+ return false;
6980
+ }
6981
+ };
6982
+ var defaultRealpath = (p) => {
6983
+ try {
6984
+ return import_fs2.realpathSync.native(p);
6985
+ } catch {
6986
+ return p;
6987
+ }
6988
+ };
6989
+ function searchGit(workspace, o) {
6990
+ const platform = o.platform ?? process.platform;
6991
+ const win = platform === "win32";
6992
+ const p = win ? import_path2.win32 : import_path2.posix;
6993
+ const env = o.env ?? process.env;
6994
+ const isExecutable = o.isExecutable ?? defaultIsExecutable(platform);
6995
+ const realpath = o.realpath ?? defaultRealpath;
6996
+ const pathKey = (win ? Object.keys(env).find((k) => k.toUpperCase() === "PATH") : void 0) ?? "PATH";
6997
+ const pathValue = env[pathKey];
6998
+ const pathExt = win ? Object.entries(env).find(([k]) => k.toUpperCase() === "PATHEXT")?.[1] : void 0;
6999
+ const exts = win ? [...new Set((pathExt ?? ".COM;.EXE").split(";").map((e) => e.trim().toLowerCase()).filter((e) => e === ".exe" || e === ".com"))] : [""];
7000
+ if (win && !exts.length) exts.push(".exe");
7001
+ const roots = [.../* @__PURE__ */ new Set([p.resolve(workspace), p.resolve(realpath(workspace))])];
7002
+ const inside = (dir) => roots.some((root) => {
7003
+ const rel = p.relative(root, dir);
7004
+ return !(rel === ".." || rel.startsWith(`..${p.sep}`) || p.isAbsolute(rel));
6875
7005
  });
7006
+ const absolute = (dir) => win ? /^([A-Za-z]:[\\/]|\\\\[^\\])/.test(dir) : dir.startsWith("/");
7007
+ const kept = [];
7008
+ let git = null;
7009
+ for (const raw of (pathValue ?? "").split(win ? ";" : ":")) {
7010
+ const dir = win ? raw.trim().replace(/^"(.*)"$/, "$1") : raw;
7011
+ if (!dir || dir === "." || !absolute(dir)) continue;
7012
+ const resolved = p.resolve(dir);
7013
+ if (inside(resolved) || inside(p.resolve(realpath(resolved)))) continue;
7014
+ kept.push(raw);
7015
+ for (const ext of git ? [] : exts) {
7016
+ const candidate = p.join(resolved, `git${ext}`);
7017
+ if (isExecutable(candidate)) {
7018
+ git = candidate;
7019
+ break;
7020
+ }
7021
+ }
7022
+ }
7023
+ return { git, path: kept.join(win ? ";" : ":"), pathKey };
6876
7024
  }
6877
- var outside = (rel) => !rel || rel.startsWith("..") || (0, import_path.isAbsolute)(rel) || /^[A-Za-z]:/.test(rel);
6878
- async function refusalFor(absolute, path, workspace) {
6879
- if (path.split("/").includes(".git")) return `${path} is inside a .git directory and was not uploaded`;
6880
- const [real, root] = await Promise.all([import_fs2.promises.realpath(absolute), import_fs2.promises.realpath(workspace)]);
6881
- if (outside((0, import_path.relative)(root, real))) return `${path} resolves outside the workspace (symlink) and was not uploaded`;
6882
- return null;
7025
+ function findGit(workspace, o = {}) {
7026
+ return searchGit(workspace, o).git;
6883
7027
  }
6884
- async function collectArtifacts(patterns, workspace, deps) {
6885
- const warnings = [];
6886
- if (!patterns.length) return { files: [], warnings };
6887
- const find = deps.glob;
6888
- const inside = [];
6889
- for (const absolute of [...new Set(await find(patterns))]) {
6890
- const rel = (0, import_path.relative)(workspace, absolute);
6891
- if (outside(rel)) {
6892
- warnings.push(`${absolute} is outside the workspace and was not uploaded`);
7028
+ function gitEnv(workspace, o = {}) {
7029
+ const search = searchGit(workspace, o);
7030
+ const env = { ...o.env ?? process.env };
7031
+ for (const k of Object.keys(env)) if (k.toUpperCase() === "PATH") delete env[k];
7032
+ env[search.pathKey] = search.path;
7033
+ return env;
7034
+ }
7035
+ function gitRunnerFor(lookup = {}, spawn = import_child_process.execFile) {
7036
+ const found = /* @__PURE__ */ new Map();
7037
+ return {
7038
+ run: (args, { cwd, timeoutMs }) => new Promise((resolveRun, reject) => {
7039
+ if (!found.has(cwd)) found.set(cwd, { git: findGit(cwd, lookup), env: gitEnv(cwd, lookup) });
7040
+ const { git, env: baseEnv } = found.get(cwd);
7041
+ if (!git) {
7042
+ reject(new GitError("git not found", "", "ENOENT"));
7043
+ return;
7044
+ }
7045
+ spawn(git, args, {
7046
+ cwd,
7047
+ timeout: Math.max(1, timeoutMs),
7048
+ maxBuffer: 16 * 1024 * 1024,
7049
+ windowsHide: true,
7050
+ encoding: "utf8",
7051
+ // English messages (for missing-path detection) and never a credential prompt.
7052
+ env: { ...baseEnv, LC_ALL: "C", GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0" }
7053
+ }, (err, stdout, stderr) => {
7054
+ if (err) reject(new GitError("git failed", String(stderr ?? ""), err.code));
7055
+ else resolveRun(String(stdout));
7056
+ });
7057
+ })
7058
+ };
7059
+ }
7060
+ var execGit = gitRunnerFor();
7061
+ function normalizeSnippet(lines) {
7062
+ return lines.map((l) => l.trim().replace(/\s+/g, " ")).filter(Boolean).join("\n");
7063
+ }
7064
+ function snippetHashOf(lines) {
7065
+ const snippet = normalizeSnippet(lines);
7066
+ return snippet ? (0, import_crypto2.createHash)("sha256").update(snippet).digest("hex").slice(0, 32) : void 0;
7067
+ }
7068
+ var isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7069
+ var arr = (v) => Array.isArray(v) ? v : [];
7070
+ var str = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
7071
+ var SCA_TOOLS = [
7072
+ /\bgrype\b|anchore\/grype/i,
7073
+ /osv-scanner|google\/osv-scanner/i,
7074
+ /dependency-check|dependencycheck/i,
7075
+ /snyk open source|snyk-open-source/i,
7076
+ /\bnpm[- ]audit\b/i,
7077
+ /\byarn[- ]audit\b/i,
7078
+ /\bpnpm[- ]audit\b/i,
7079
+ /\bpip[- ]audit\b/i,
7080
+ /\bcargo[- ]audit\b/i,
7081
+ /\bbundler[- ]audit\b/i,
7082
+ /\bgovulncheck\b/i
7083
+ ];
7084
+ var SECRET_TOOLS = [/gitleaks/i, /trufflehog/i, /detect-secrets/i, /ggshield|gitguardian/i, /secret[- ]scanning/i, /secretlint/i, /noseyparker|nosey parker/i];
7085
+ var MANIFESTS = [
7086
+ /(^|\/)(package-lock\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|package\.json)$/i,
7087
+ /(^|\/)(go\.sum|go\.mod)$/i,
7088
+ /(^|\/)(Gemfile\.lock|Gemfile|gems\.locked)$/i,
7089
+ /(^|\/)(requirements[^/]*\.txt|poetry\.lock|Pipfile\.lock|Pipfile|pdm\.lock|uv\.lock|pyproject\.toml|setup\.py)$/i,
7090
+ /(^|\/)(pom\.xml|build\.gradle(\.kts)?|gradle\.lockfile|[^/]+\.jar)$/i,
7091
+ /(^|\/)(Cargo\.lock|Cargo\.toml)$/i,
7092
+ /(^|\/)(packages\.lock\.json|packages\.config|[^/]+\.csproj|[^/]+\.fsproj|[^/]+\.vbproj|[^/]+\.deps\.json)$/i,
7093
+ /(^|\/)(composer\.lock|composer\.json)$/i,
7094
+ /(^|\/)pubspec\.lock$/i,
7095
+ /(^|\/)Package\.resolved$/i,
7096
+ /(^|\/)mix\.lock$/i,
7097
+ // OS package databases inside an image.
7098
+ /(^|\/)lib\/apk\/db\/installed$/i,
7099
+ /(^|\/)var\/lib\/dpkg\/status(\.d\/[^/]+)?$/i,
7100
+ /(^|\/)var\/lib\/rpm\/(Packages|rpmdb\.sqlite|Packages\.db)$/i
7101
+ ];
7102
+ var isManifest = (path) => !!path && MANIFESTS.some((re) => re.test(path));
7103
+ var CVE = /^CVE-\d{4}-\d{4,}$/;
7104
+ var GHSA = /^GHSA(-[23456789CFGHJMPQRVWX]{4}){3}$/;
7105
+ var ADVISORY_PREFIX = /^(RUSTSEC|GO|PYSEC|OSV|GSD|MAL|SNYK|RUBYSEC|DLA|DSA|USN|ALSA|RHSA|ELSA|ALAS\d*|BIT|CGA|MGASA|OESA|OPENSUSE|SUSE)-/;
7106
+ var LEADING_VULN_ID = /^(CVE-\d{4}-\d{4,}|GHSA(?:-[23456789cfghjmpqrvwx]{4}){3})-([^-].*)$/i;
7107
+ var CHECKOV_SCA_ID = /^([A-Z]+-[A-Z0-9-]+?)_(.+)@([^@]+)$/i;
7108
+ function normalizeVulnId(v) {
7109
+ if (typeof v !== "string") return null;
7110
+ const id = v.trim().toUpperCase();
7111
+ if (!id || id.length > 64 || !/^[A-Z0-9._:-]+$/.test(id)) return null;
7112
+ if (id.startsWith("CVE-") && !CVE.test(id)) return null;
7113
+ if (id.startsWith("GHSA-") && !GHSA.test(id)) return null;
7114
+ return id;
7115
+ }
7116
+ var isAdvisoryId = (id) => !!id && (CVE.test(id) || GHSA.test(id) || ADVISORY_PREFIX.test(id));
7117
+ function vulnFromRuleId(ruleId) {
7118
+ if (!ruleId) return { id: null, rest: null, pkgVersion: null };
7119
+ const whole = normalizeVulnId(ruleId);
7120
+ if (isAdvisoryId(whole)) return { id: whole, rest: null, pkgVersion: null };
7121
+ const lead = LEADING_VULN_ID.exec(ruleId);
7122
+ if (lead) return { id: normalizeVulnId(lead[1]), rest: lead[2], pkgVersion: null };
7123
+ const ck = CHECKOV_SCA_ID.exec(ruleId);
7124
+ const ckId = ck ? normalizeVulnId(ck[1]) : null;
7125
+ if (ck && isAdvisoryId(ckId)) return { id: ckId, rest: ck[2], pkgVersion: ck[3] };
7126
+ return { id: null, rest: null, pkgVersion: null };
7127
+ }
7128
+ var canon = (k) => k.toLowerCase().replace(/[-_ ]/g, "");
7129
+ var prop = (bag, names) => {
7130
+ if (!isObj(bag)) return void 0;
7131
+ for (const key of Object.keys(bag)) if (names.includes(canon(key))) return bag[key];
7132
+ return void 0;
7133
+ };
7134
+ var firstStr = (v) => Array.isArray(v) ? v.map(str).find(Boolean) ?? null : str(v);
7135
+ var nameValue = (v) => {
7136
+ if (isObj(v)) return str(v.name);
7137
+ const s = firstStr(v);
7138
+ return s && s.length <= 300 && !/\s/.test(s) ? s : null;
7139
+ };
7140
+ var concreteVersion = (v) => v && v.length <= 100 && /^[A-Za-z0-9][A-Za-z0-9._+:~-]*$/.test(v) ? v : null;
7141
+ function parsePurl(purl) {
7142
+ if (typeof purl !== "string" || purl.length > 2048 || !purl.startsWith("pkg:")) return null;
7143
+ const withoutSub = purl.slice(4).split("#")[0].split("?")[0];
7144
+ const at = withoutSub.lastIndexOf("@");
7145
+ const parts = (at > 0 ? withoutSub.slice(0, at) : withoutSub).split("/").filter(Boolean);
7146
+ if (parts.length < 2 || !/^[a-z][a-z0-9.+-]*$/.test(parts[0].toLowerCase()) || !parts[parts.length - 1]) return null;
7147
+ let version = null;
7148
+ if (at > 0) {
7149
+ const raw = withoutSub.slice(at + 1);
7150
+ try {
7151
+ version = decodeURIComponent(raw) || null;
7152
+ } catch {
7153
+ version = raw || null;
7154
+ }
7155
+ }
7156
+ return { version };
7157
+ }
7158
+ var PROP = {
7159
+ purl: ["purl", "purls", "packageurl", "packageurls"],
7160
+ name: ["packagename", "pkgname", "package", "componentname", "dependency", "dependencyname", "artifactname", "libraryname"],
7161
+ version: ["packageversion", "pkgversion", "installedversion", "componentversion", "dependencyversion", "currentversion", "version"],
7162
+ fixed: ["fixedversion", "fixedversions", "fixversion", "fixversions", "patchedversion", "patchedversions", "fixedin"]
7163
+ };
7164
+ function fromProperties(bag) {
7165
+ if (!isObj(bag)) return null;
7166
+ const purlText = firstStr(prop(bag, PROP.purl));
7167
+ const parsed = parsePurl(purlText);
7168
+ const pkgObj = isObj(prop(bag, ["package"])) ? prop(bag, ["package"]) : null;
7169
+ const name = nameValue(prop(bag, PROP.name)) ?? (parsed ? "purl" : null);
7170
+ const versionRaw = firstStr(prop(bag, PROP.version.filter((n) => n !== "version"))) ?? (name ? firstStr(prop(bag, ["version"])) : null) ?? str(pkgObj?.version);
7171
+ const fixed = firstStr(prop(bag, PROP.fixed));
7172
+ if (!parsed && !name && !versionRaw && !fixed) return null;
7173
+ return { purl: parsed ? purlText : null, name, version: concreteVersion(versionRaw) ?? parsed?.version ?? null, fixedVersion: fixed };
7174
+ }
7175
+ function mergeParts(parts) {
7176
+ const out = { purl: null, name: null, version: null, fixedVersion: null };
7177
+ for (const p of parts) {
7178
+ if (!p) continue;
7179
+ for (const k of Object.keys(out)) if (!out[k] && p[k]) out[k] = p[k];
7180
+ }
7181
+ return out;
7182
+ }
7183
+ var PACKAGE_MESSAGES = [/^Package: *(\S+)\s*$/m, /^Package '([^']+)@([^'@]+)' is vulnerable to '/, /vulnerability in ([\w.-]+) package: ([^,\s]+), version ([^\s,]+) was found/];
7184
+ var SNYK_DESCRIPTION = /^\(([^)]*)\)\s+(\S+)@(\S+)$/;
7185
+ function isDependencyResult(result, rule, run2, path, line) {
7186
+ if (isManifest(path) && line === 1) return true;
7187
+ const driver = isObj(run2.tool?.driver) ? run2.tool.driver : {};
7188
+ const props = isObj(result.properties) ? result.properties : {};
7189
+ const toolName = str(props.scanner_name) ?? str(props.scanner_details?.tool_name) ?? str(driver.name);
7190
+ const nested = toolName !== str(driver.name);
7191
+ const toolNames = [toolName, nested ? null : str(driver.fullName), nested ? null : str(driver.informationUri)].filter((n) => !!n);
7192
+ const tags = [...arr(rule.properties?.tags), ...arr(result.properties?.tags)].filter((t) => typeof t === "string").map((t) => t.toLowerCase());
7193
+ const toolType = tags.map((t) => /^tool_type::(\w+)$/.exec(t)?.[1]).find(Boolean) ?? null;
7194
+ if (toolNames.some((n) => SECRET_TOOLS.some((re) => re.test(n))) || toolType === "secrets" || tags.includes("secret") || tags.includes("secrets")) return false;
7195
+ if (toolType === "sast" || toolType === "iac" || tags.includes("misconfiguration")) return false;
7196
+ if (toolNames.some((n) => SCA_TOOLS.some((re) => re.test(n))) || toolType === "sca" || toolType === "container") return true;
7197
+ const parts = mergeParts([fromProperties(result.properties), fromProperties(rule.properties)]);
7198
+ const explicitName = [result.properties, rule.properties].some((b) => !!nameValue(prop(b, ["packagename", "pkgname"])));
7199
+ if (parts.purl || explicitName || parts.name && (parts.version || parts.fixedVersion)) return true;
7200
+ const message = typeof result.message?.text === "string" ? result.message.text : "";
7201
+ if (PACKAGE_MESSAGES.some((re) => re.test(message))) return true;
7202
+ const ruleVuln = vulnFromRuleId(str(result.ruleId) ?? str(rule.id));
7203
+ const snyk = SNYK_DESCRIPTION.exec(str(rule.fullDescription?.text) ?? "");
7204
+ const vulnIds = [
7205
+ ruleVuln.id,
7206
+ ...[...arr(rule.deprecatedIds), ...arr(rule.properties?.aliases)].map(normalizeVulnId).filter(isAdvisoryId),
7207
+ ...snyk ? snyk[1].split(/[,\s]+/).map(normalizeVulnId).filter(isAdvisoryId) : []
7208
+ ].filter(Boolean);
7209
+ if (!vulnIds.length) return false;
7210
+ if (ruleVuln.rest && ruleVuln.pkgVersion) return true;
7211
+ if (isManifest(path) || str(run2.properties?.imageName)) return true;
7212
+ return tags.some((t) => ["vulnerability", "dependency", "dependencies", "sca", "supply-chain", "security"].includes(t));
7213
+ }
7214
+ function hasFingerprints(result) {
7215
+ return [result.partialFingerprints, result.fingerprints].some((source) => isObj(source) && Object.entries(source).some(([key, value]) => typeof value === "string" && (/(hash|fingerprint)/i.test(key) || /\/v\d+$/.test(key))));
7216
+ }
7217
+ var rulesById = /* @__PURE__ */ new WeakMap();
7218
+ var hasRuleId = (result, run2) => !!(str(result.ruleId) ?? str(ruleFor(result, run2).id));
7219
+ function ruleFor(result, run2) {
7220
+ const driverRules = arr(run2.tool?.driver?.rules);
7221
+ const ref = isObj(result.rule) ? result.rule : {};
7222
+ const idx = Number.isInteger(result.ruleIndex) ? result.ruleIndex : Number.isInteger(ref.index) ? ref.index : null;
7223
+ if (idx !== null && !isObj(ref.toolComponent) && isObj(driverRules[idx])) return driverRules[idx];
7224
+ const id = str(result.ruleId) ?? str(ref.id);
7225
+ let byId = rulesById.get(run2);
7226
+ if (!byId) {
7227
+ byId = /* @__PURE__ */ new Map();
7228
+ for (const r of [...driverRules, ...arr(run2.tool?.extensions).flatMap((e) => arr(e?.rules))]) {
7229
+ if (isObj(r) && !byId.has(r.id)) byId.set(r.id, r);
7230
+ }
7231
+ rulesById.set(run2, byId);
7232
+ }
7233
+ return byId.get(id) ?? {};
7234
+ }
7235
+ function uriToPath(uri) {
7236
+ if (/^file:/i.test(uri)) {
7237
+ try {
7238
+ return (0, import_url.fileURLToPath)(uri);
7239
+ } catch {
7240
+ return null;
7241
+ }
7242
+ }
7243
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(uri)) return null;
7244
+ try {
7245
+ return decodeURIComponent(uri);
7246
+ } catch {
7247
+ return uri;
7248
+ }
7249
+ }
7250
+ function categoryPath(uri) {
7251
+ let p = uri.trim();
7252
+ try {
7253
+ p = decodeURIComponent(p);
7254
+ } catch {
7255
+ }
7256
+ p = p.replace(/\\/g, "/").replace(/^file:\/\//i, "");
7257
+ p = p.replace(/^\/github\/workspace\//, "").replace(/^\/home\/runner\/work\/[^/]+\/[^/]+\//, "");
7258
+ p = p.replace(/^\.\//, "").replace(/^\/+/, "").replace(/\/{2,}/g, "/");
7259
+ return !p || p.split("/").some((s) => s === "..") ? null : p;
7260
+ }
7261
+ var outsideRel = (rel) => !rel || rel === ".." || rel.startsWith(`..${import_path2.sep}`) || (0, import_path2.isAbsolute)(rel) || /^[A-Za-z]:/.test(rel);
7262
+ function locate(result, run2, workspace) {
7263
+ const physical = arr(result.locations).map((l) => l?.physicalLocation).find(isObj);
7264
+ const al = isObj(physical?.artifactLocation) ? physical.artifactLocation : {};
7265
+ const fromIndex = Number.isInteger(al.index) ? arr(run2.artifacts)[al.index]?.location : null;
7266
+ const loc = typeof al.uri === "string" ? al : isObj(fromIndex) ? fromIndex : null;
7267
+ const uri = loc && typeof loc.uri === "string" && loc.uri.trim() ? loc.uri.trim() : null;
7268
+ const rawStart = physical?.region?.startLine;
7269
+ const start = Number.isInteger(rawStart) && rawStart >= 1 ? rawStart : null;
7270
+ const rawEnd = physical?.region?.endLine;
7271
+ const end = start === null ? 0 : Math.min(Number.isInteger(rawEnd) && rawEnd >= start ? rawEnd : start, start + MAX_RANGE_LINES - 1);
7272
+ const located = (kind, path2 = null) => ({ kind, path: path2, categoryPath: uri ? categoryPath(uri) : null, start, end });
7273
+ if (!uri || start === null) return located("none");
7274
+ let p = uriToPath(uri);
7275
+ if (p === null) return located("outside");
7276
+ const base = typeof loc?.uriBaseId === "string" ? run2.originalUriBaseIds?.[loc.uriBaseId]?.uri : null;
7277
+ const basePath = typeof base === "string" ? uriToPath(base) : null;
7278
+ if (!(0, import_path2.isAbsolute)(p) && !p.startsWith("/") && basePath) p = (0, import_path2.resolve)(workspace, basePath, p);
7279
+ p = p.replace(/^\/github\/workspace\//, "");
7280
+ const rel = (0, import_path2.relative)(workspace, (0, import_path2.resolve)(workspace, p));
7281
+ if (outsideRel(rel)) return located("outside");
7282
+ const path = rel.split(import_path2.sep).join("/");
7283
+ if (path.split("/").includes(".git")) return located("outside");
7284
+ return located("ok", path);
7285
+ }
7286
+ function parsePorcelain(out) {
7287
+ const commits = /* @__PURE__ */ new Map();
7288
+ const lines = [];
7289
+ const order = [];
7290
+ let current = null;
7291
+ for (const raw of out.split("\n")) {
7292
+ const header = /^([0-9a-f]{40,64}) \d+ \d+(?: \d+)?$/.exec(raw);
7293
+ if (header) {
7294
+ current = header[1];
7295
+ if (!commits.has(current)) commits.set(current, { time: 0, boundary: false });
6893
7296
  continue;
6894
7297
  }
6895
- const path = rel.split(import_path.sep).join("/");
6896
- const refused = await refusalFor(absolute, path, workspace);
6897
- if (refused) {
6898
- warnings.push(refused);
7298
+ if (!current) continue;
7299
+ if (raw.startsWith(" ")) {
7300
+ lines.push(raw.slice(1));
7301
+ order.push(current);
6899
7302
  continue;
6900
7303
  }
6901
- inside.push({ absolute, path });
7304
+ const info = commits.get(current);
7305
+ if (raw.startsWith("committer-time ")) info.time = Number(raw.slice(15)) || 0;
7306
+ else if (raw === "boundary") info.boundary = true;
6902
7307
  }
6903
- inside.sort((a, b) => a.path.localeCompare(b.path));
6904
- if (inside.length > MAX_ARTIFACT_FILES) warnings.push(`${inside.length} files matched; only the first ${MAX_ARTIFACT_FILES} are uploaded`);
6905
- const files = [];
6906
- for (const f of inside.slice(0, MAX_ARTIFACT_FILES)) {
6907
- const stat = await import_fs2.promises.stat(f.absolute);
6908
- if (!stat.isFile() || stat.size === 0) continue;
6909
- files.push({ path: f.path, absolute: f.absolute, size: stat.size, sha256: await sha256File(f.absolute), contentType: contentTypeFor(f.path) });
7308
+ if (!lines.length) return { ok: false, reason: "error" };
7309
+ if (lines.some((l) => l.includes("\0"))) return { ok: false, reason: "binary" };
7310
+ let best = order[0];
7311
+ for (const c of order) if (commits.get(c).time > commits.get(best).time) best = c;
7312
+ return { ok: true, commit: best, boundary: commits.get(best).boundary, lines };
7313
+ }
7314
+ function failureReason(text) {
7315
+ if (/no such path|has only \d+ lines?|does not exist|outside repository|no such file/i.test(text)) return "missing";
7316
+ if (/binary/i.test(text)) return "binary";
7317
+ return "error";
7318
+ }
7319
+ async function buildBlameSidecar(sarif, opts) {
7320
+ const started = Date.now();
7321
+ const budget = opts.budget ?? createBlameBudget(opts, started);
7322
+ if (budget.gitMissing) return null;
7323
+ const git = opts.git ?? execGit;
7324
+ const cwd = opts.cwd;
7325
+ const safeDirectory = cwd.replace(/\\/g, "/");
7326
+ const gitRun = (args, timeoutMs) => git.run(["-c", `safe.directory=${safeDirectory}`, "-c", "safe.bareRepository=explicit", ...args], { cwd, timeoutMs });
7327
+ const callMs = () => Math.min(MAX_CALL_MS, budget.deadline - Date.now());
7328
+ let doc;
7329
+ let sarifSha256;
7330
+ try {
7331
+ sarifSha256 = (0, import_crypto2.createHash)("sha256").update(sarif).digest("hex");
7332
+ doc = JSON.parse(Buffer.from(sarif.buffer, sarif.byteOffset, sarif.byteLength).toString("utf8"));
7333
+ if (!isObj(doc)) return null;
7334
+ } catch {
7335
+ return null;
6910
7336
  }
6911
- return { files, warnings };
7337
+ let head;
7338
+ let shallow;
7339
+ try {
7340
+ head = (await gitRun(["rev-parse", "HEAD"], MAX_CALL_MS)).trim();
7341
+ if (!/^[0-9a-f]{40,64}$/.test(head)) return null;
7342
+ shallow = (await gitRun(["rev-parse", "--is-shallow-repository"], MAX_CALL_MS).catch(() => "false")).trim() === "true";
7343
+ } catch (e) {
7344
+ if (e?.code === "ENOENT") budget.gitMissing = true;
7345
+ return null;
7346
+ }
7347
+ const cache = /* @__PURE__ */ new Map();
7348
+ const missingFiles = /* @__PURE__ */ new Set();
7349
+ const shallowBoundary = /* @__PURE__ */ new Map();
7350
+ let canDeepen = shallow;
7351
+ const blameRange = (path, start, end) => {
7352
+ const key = `${start},${end}\0${path}`;
7353
+ let hit = cache.get(key);
7354
+ if (!hit) {
7355
+ hit = (async () => {
7356
+ if (missingFiles.has(path)) return { ok: false, reason: "missing" };
7357
+ const timeoutMs = callMs();
7358
+ if (timeoutMs <= 0) return { ok: false, reason: "limit" };
7359
+ try {
7360
+ return parsePorcelain(await gitRun(["blame", "--no-textconv", "-w", "-M", "-C", "--porcelain", "-L", `${start},${end}`, head, "--", path], timeoutMs));
7361
+ } catch (e) {
7362
+ if (Date.now() >= budget.deadline) return { ok: false, reason: "limit" };
7363
+ const stderr = e instanceof GitError ? e.stderr : "";
7364
+ if (/no such path/i.test(stderr)) missingFiles.add(path);
7365
+ const lineCount = Number(/has only (\d+) lines?/i.exec(stderr)?.[1]) || void 0;
7366
+ return { ok: false, reason: failureReason(stderr), lineCount };
7367
+ }
7368
+ })();
7369
+ cache.set(key, hit);
7370
+ }
7371
+ return hit;
7372
+ };
7373
+ const blame = async (path, start, end) => {
7374
+ const first = await blameRange(path, start, end);
7375
+ const lineCount = first.ok ? null : first.lineCount;
7376
+ return lineCount && start <= lineCount && end > lineCount ? blameRange(path, start, lineCount) : first;
7377
+ };
7378
+ const isShallowEdge = (commit, timeoutMs) => {
7379
+ let hit = shallowBoundary.get(commit);
7380
+ if (!hit) {
7381
+ hit = gitRun(["cat-file", "commit", commit], timeoutMs).then((raw) => /^parent [0-9a-f]+$/m.test(raw), () => true);
7382
+ shallowBoundary.set(commit, hit);
7383
+ }
7384
+ return hit;
7385
+ };
7386
+ const deepen = async () => {
7387
+ if (!canDeepen || budget.deepensLeft <= 0) return false;
7388
+ budget.deepensLeft--;
7389
+ try {
7390
+ await gitRun(["-c", "http.lowSpeedLimit=1000", "-c", "http.lowSpeedTime=10", "fetch", `--deepen=${DEEPEN_BY}`, "--no-tags", "--quiet"], Math.max(1, budget.deadline - Date.now()));
7391
+ } catch {
7392
+ canDeepen = false;
7393
+ return false;
7394
+ }
7395
+ cache.clear();
7396
+ shallowBoundary.clear();
7397
+ return true;
7398
+ };
7399
+ const linesAtHead = (path, timeoutMs) => gitRun(["show", "--no-textconv", `${head}:./${path}`], timeoutMs).then(
7400
+ (text) => text.includes("\0") ? null : text.split("\n").map((l) => l.replace(/\r$/, "")),
7401
+ () => null
7402
+ );
7403
+ const toHash = [];
7404
+ const hashSnippets = async () => {
7405
+ const byPath = /* @__PURE__ */ new Map();
7406
+ for (const t of toHash) {
7407
+ const group = byPath.get(t.path);
7408
+ if (group) group.push(t);
7409
+ else byPath.set(t.path, [t]);
7410
+ }
7411
+ if (!byPath.size) return;
7412
+ const deadline = budget.snippetDeadline ??= Date.now() + budget.snippetMs;
7413
+ for (const [path, group] of byPath) {
7414
+ const timeoutMs = Math.min(MAX_CALL_MS, deadline - Date.now());
7415
+ if (budget.snippetsLeft <= 0 || timeoutMs <= 0) return;
7416
+ const counted = group.slice(0, budget.snippetsLeft);
7417
+ budget.snippetsLeft -= counted.length;
7418
+ const lines = await linesAtHead(path, timeoutMs);
7419
+ if (!lines) continue;
7420
+ const count = lines.at(-1) === "" ? lines.length - 1 : lines.length;
7421
+ for (const { entry, start, end } of counted) {
7422
+ const hash = start > count ? void 0 : snippetHashOf(lines.slice(start - 1, Math.min(end, count)));
7423
+ if (hash) entry.snippetHash = hash;
7424
+ }
7425
+ }
7426
+ };
7427
+ const results = [];
7428
+ const runs = arr(doc.runs).slice(0, MAX_RUNS);
7429
+ for (let r = 0; r < runs.length; r++) {
7430
+ const run2 = isObj(runs[r]) ? runs[r] : {};
7431
+ const list2 = arr(run2.results);
7432
+ for (let i = 0; i < list2.length; i++) {
7433
+ const result = list2[i];
7434
+ if (!isObj(result) || SKIP_KINDS.has(result.kind)) continue;
7435
+ const entry = { run: r, result: i, status: "skipped" };
7436
+ results.push(entry);
7437
+ try {
7438
+ const located = locate(result, run2, cwd);
7439
+ if (isDependencyResult(result, ruleFor(result, run2), run2, located.categoryPath, located.start)) {
7440
+ Object.assign(entry, { status: "not_applicable", reason: "dependency" });
7441
+ continue;
7442
+ }
7443
+ if (located.kind === "none") {
7444
+ entry.reason = "no_location";
7445
+ continue;
7446
+ }
7447
+ if (located.kind === "outside" || !located.path || located.start === null) {
7448
+ entry.reason = "missing";
7449
+ continue;
7450
+ }
7451
+ const { path, start, end } = { path: located.path, start: located.start, end: located.end };
7452
+ if (!hasFingerprints(result) && hasRuleId(result, run2) && toHash.length < budget.snippetsLeft && (budget.snippetDeadline === null || Date.now() < budget.snippetDeadline)) {
7453
+ toHash.push({ entry, path, start, end });
7454
+ }
7455
+ if (budget.resultsLeft <= 0 || Date.now() >= budget.deadline) {
7456
+ entry.reason = "limit";
7457
+ continue;
7458
+ }
7459
+ budget.resultsLeft--;
7460
+ let blamed = await blame(path, start, end);
7461
+ while (blamed.ok && shallow && blamed.boundary) {
7462
+ const timeoutMs = callMs();
7463
+ if (timeoutMs <= 0) {
7464
+ entry.reason = "limit";
7465
+ break;
7466
+ }
7467
+ if (!await isShallowEdge(blamed.commit, timeoutMs)) break;
7468
+ if (!await deepen()) {
7469
+ entry.reason = Date.now() >= budget.deadline ? "limit" : "shallow";
7470
+ break;
7471
+ }
7472
+ blamed = await blame(path, start, end);
7473
+ }
7474
+ if (entry.reason) continue;
7475
+ if (!blamed.ok) {
7476
+ entry.reason = blamed.reason;
7477
+ continue;
7478
+ }
7479
+ Object.assign(entry, { status: "found", commit: blamed.commit });
7480
+ } catch {
7481
+ entry.reason = "error";
7482
+ }
7483
+ }
7484
+ }
7485
+ try {
7486
+ await hashSnippets();
7487
+ } catch {
7488
+ }
7489
+ return {
7490
+ format: "prodgator-blame",
7491
+ version: 1,
7492
+ sarifSha256,
7493
+ head,
7494
+ results,
7495
+ stats: { blamed: results.filter((e) => e.status === "found").length, skipped: results.filter((e) => e.status === "skipped").length, ms: Date.now() - started }
7496
+ };
6912
7497
  }
6913
7498
 
6914
7499
  // src/core/attestations.ts
6915
7500
  var KINDS = ["test-results", "coverage", "sbom", "scan", "custom", "provenance"];
6916
- function parseAttestationsInput(value, workspace) {
7501
+ async function parseAttestationsInput(value, workspace) {
6917
7502
  if (!value) return [];
6918
- const text = /^\s*[[{]/.test(value) ? value : (0, import_fs3.readFileSync)((0, import_path2.resolve)(workspace, value.trim()), "utf8");
7503
+ let text = value;
7504
+ if (!/^\s*[[{]/.test(value)) {
7505
+ const read = await readWorkspaceText(workspace, value.trim());
7506
+ if (!read.ok) throw new Error(`attestations file ${read.message}; it was not read`);
7507
+ text = read.text ?? "";
7508
+ }
6919
7509
  let parsed;
6920
7510
  try {
6921
7511
  parsed = JSON.parse(text);
@@ -6937,7 +7527,7 @@ function contentTypeForScanFile(format, path) {
6937
7527
  if (format === "sarif") return "application/sarif+json";
6938
7528
  if (format === "cyclonedx") return "application/vnd.cyclonedx+json";
6939
7529
  if (format === "spdx") return "application/spdx+json";
6940
- return (0, import_path2.extname)(path).toLowerCase() === ".json" ? "application/json" : "application/octet-stream";
7530
+ return (0, import_path3.extname)(path).toLowerCase() === ".json" ? "application/json" : "application/octet-stream";
6941
7531
  }
6942
7532
  function sarifCounts(json) {
6943
7533
  const counts = { critical: 0, high: 0, medium: 0, low: 0 };
@@ -6968,13 +7558,77 @@ function sarifCounts(json) {
6968
7558
  }
6969
7559
  return counts;
6970
7560
  }
7561
+ var BLAME_INPUT_WARNING = "data.blame is set by the report client itself; the value given was ignored";
7562
+ var isReservedSidecarPath = (path) => path.toLowerCase().endsWith(BLAME_SIDECAR_SUFFIX);
7563
+ var BLAME_NO_GIT_NOTE = "git is not installed, so code findings were sent without blame data.";
7564
+ var blameTooLargeWarning = (file) => `${file} is too large to add blame data to (over ${MAX_SARIF_BYTES} bytes), so its code findings were sent without it`;
7565
+ var BlameSidecars = class {
7566
+ constructor(limits, build) {
7567
+ this.build = build;
7568
+ this.budget = createBlameBudget(limits);
7569
+ }
7570
+ dir = null;
7571
+ count = 0;
7572
+ budget;
7573
+ /** The sidecar as an artifact, or why there is none (null when git is not installed: the note covers it). */
7574
+ async add(sarif, path, workspace) {
7575
+ let sidecar;
7576
+ try {
7577
+ sidecar = await this.build(sarif, { cwd: workspace, budget: this.budget });
7578
+ } catch {
7579
+ return "blame data was not added (blame stopped with an error)";
7580
+ }
7581
+ if (this.budget.gitMissing) return null;
7582
+ if (!sidecar) return "blame data was not added (the workspace is not a git checkout, or the SARIF file could not be read)";
7583
+ try {
7584
+ this.dir ??= await import_fs3.promises.mkdtemp((0, import_path3.join)((0, import_os.tmpdir)(), "prodgator-blame-"));
7585
+ const name = `${++this.count}${BLAME_SIDECAR_SUFFIX}`;
7586
+ const target = (0, import_path3.join)(this.dir, name);
7587
+ const text = JSON.stringify(sidecar);
7588
+ await import_fs3.promises.writeFile(target, text, { flag: "wx" });
7589
+ return {
7590
+ // Uploaded from the directory this client made (its own root for the upload check), never from the checkout.
7591
+ path: path + BLAME_SIDECAR_SUFFIX,
7592
+ absolute: target,
7593
+ workspace: this.dir,
7594
+ source: name,
7595
+ size: Buffer.byteLength(text),
7596
+ sha256: (0, import_crypto3.createHash)("sha256").update(text).digest("hex"),
7597
+ contentType: "application/json"
7598
+ };
7599
+ } catch {
7600
+ return "blame data was not added (the blame file could not be written)";
7601
+ }
7602
+ }
7603
+ /** Removes the temporary directory (best effort). Call after the uploads. */
7604
+ async cleanup() {
7605
+ const dir = this.dir;
7606
+ this.dir = null;
7607
+ if (dir) await import_fs3.promises.rm(dir, { recursive: true, force: true }).catch(() => {
7608
+ });
7609
+ }
7610
+ };
6971
7611
  async function resolveAttestationFiles(attestations, workspace, deps = {}) {
6972
- const hash = deps.sha256File ?? sha256File;
7612
+ const blame = deps.blame ? new BlameSidecars(deps.blame === true ? {} : deps.blame, deps.buildBlameSidecar ?? buildBlameSidecar) : null;
7613
+ try {
7614
+ return await resolveFiles(attestations, workspace, blame);
7615
+ } catch (e) {
7616
+ await blame?.cleanup();
7617
+ throw e;
7618
+ }
7619
+ }
7620
+ async function resolveFiles(attestations, workspace, blame) {
6973
7621
  const files = [];
6974
7622
  const warnings = [];
6975
7623
  const out = [];
7624
+ let warnedTooLargeForBlame = false;
6976
7625
  for (const input of attestations) {
6977
7626
  let a = input;
7627
+ if ("blame" in a.data) {
7628
+ const { blame: _given, ...rest } = a.data;
7629
+ a = { ...a, data: rest };
7630
+ warnings.push(BLAME_INPUT_WARNING);
7631
+ }
6978
7632
  if (a.kind === "scan" && "mode" in a.data && a.data.mode !== "full" && a.data.mode !== "diff") {
6979
7633
  const { mode: _ignored, ...rest } = a.data;
6980
7634
  a = { ...a, data: rest };
@@ -6987,39 +7641,123 @@ async function resolveAttestationFiles(attestations, workspace, deps = {}) {
6987
7641
  }
6988
7642
  const format = a.format;
6989
7643
  const scanFormat = typeof format === "string" && SCAN_FILE_FORMATS.includes(format) ? format : void 0;
6990
- const absolute = (0, import_path2.resolve)(workspace, file);
6991
- const rel = (0, import_path2.relative)(workspace, absolute);
6992
- if (!rel || rel.startsWith("..") || /^[A-Za-z]:/.test(rel)) {
6993
- warnings.push(`${file} is outside the workspace and was not attached to "${a.name}"`);
7644
+ const wantCounts = a.kind === "scan" && scanFormat === "sarif" && a.data.critical === void 0 && a.data.high === void 0 && a.data.medium === void 0 && a.data.low === void 0;
7645
+ const notAttached = `and was not attached to "${a.name}"`;
7646
+ const wantBlame = !!blame && a.kind === "scan" && scanFormat === "sarif";
7647
+ const opened = await openWorkspaceFile(workspace, file);
7648
+ if (!opened.ok) {
7649
+ const missing = opened.reason === "missing" || opened.reason === "not-file";
7650
+ warnings.push(missing ? `${file} was not found (or is empty) ${notAttached}` : `${opened.message} ${notAttached}`);
6994
7651
  out.push(a);
6995
7652
  continue;
6996
7653
  }
6997
- const path = rel.split(import_path2.sep).join("/");
6998
- const stat = await import_fs3.promises.stat(absolute).catch(() => null);
6999
- if (!stat?.isFile() || stat.size === 0) {
7000
- warnings.push(`${file} was not found (or is empty) and was not attached to "${a.name}"`);
7654
+ if (isReservedSidecarPath(opened.file.path)) {
7655
+ await opened.handle.close();
7656
+ warnings.push(`${opened.file.path} ends in ${BLAME_SIDECAR_SUFFIX}, a name reserved for blame data, ${notAttached}`);
7001
7657
  out.push(a);
7002
7658
  continue;
7003
7659
  }
7004
- const refused = await refusalFor(absolute, path, workspace);
7005
- if (refused) {
7006
- warnings.push(refused);
7007
- out.push(a);
7008
- continue;
7660
+ let sha256;
7661
+ let counts = null;
7662
+ let sarifBytes = null;
7663
+ try {
7664
+ if (opened.size === 0) {
7665
+ warnings.push(`${file} was not found (or is empty) ${notAttached}`);
7666
+ out.push(a);
7667
+ continue;
7668
+ }
7669
+ if ((wantCounts || wantBlame) && opened.size <= MAX_SARIF_BYTES) {
7670
+ const bytes = await opened.handle.readFile();
7671
+ sha256 = (0, import_crypto3.createHash)("sha256").update(bytes).digest("hex");
7672
+ if (wantCounts) counts = sarifCounts(bytes.toString("utf8"));
7673
+ if (wantBlame) sarifBytes = bytes;
7674
+ } else {
7675
+ sha256 = await sha256Handle(opened.handle);
7676
+ }
7677
+ } finally {
7678
+ await opened.handle.close();
7009
7679
  }
7010
- files.push({ path, absolute, size: stat.size, sha256: await hash(absolute), contentType: contentTypeForScanFile(scanFormat, path) });
7680
+ const { path, absolute } = opened.file;
7681
+ files.push({ path, absolute, workspace, size: opened.size, sha256, contentType: contentTypeForScanFile(scanFormat, path) });
7011
7682
  const data = { ...a.data, artifact: path };
7012
7683
  if (a.kind === "scan" && scanFormat) data.format = scanFormat;
7013
- if (a.kind === "scan" && scanFormat === "sarif" && data.critical === void 0 && data.high === void 0 && data.medium === void 0 && data.low === void 0) {
7014
- if (stat.size <= MAX_SARIF_BYTES) {
7015
- Object.assign(data, sarifCounts((0, import_fs3.readFileSync)(absolute, "utf8")));
7016
- } else {
7017
- warnings.push(`${file} is too large to compute SARIF counts from (over ${MAX_SARIF_BYTES} bytes); skipping`);
7684
+ if (counts) Object.assign(data, counts);
7685
+ else if (wantCounts) warnings.push(`${file} is too large to compute SARIF counts from (over ${MAX_SARIF_BYTES} bytes); skipping`);
7686
+ if (wantBlame && opened.size > MAX_SARIF_BYTES && !warnedTooLargeForBlame) {
7687
+ warnedTooLargeForBlame = true;
7688
+ warnings.push(blameTooLargeWarning(file));
7689
+ }
7690
+ if (blame && sarifBytes) {
7691
+ const sidecar = await blame.add(sarifBytes, path, workspace);
7692
+ if (typeof sidecar === "string") warnings.push(`${file}: ${sidecar}`);
7693
+ else if (sidecar && files.some((f) => f.path === sidecar.path)) warnings.push(`${file}: blame data was not added (${sidecar.path} is already in this report)`);
7694
+ else if (sidecar) {
7695
+ files.push(sidecar);
7696
+ data.blame = sidecar.path;
7018
7697
  }
7019
7698
  }
7020
7699
  out.push({ kind: a.kind, name: a.name, status: a.status, data });
7021
7700
  }
7022
- return { attestations: out, files, warnings };
7701
+ const notes = blame?.budget.gitMissing ? [BLAME_NO_GIT_NOTE] : [];
7702
+ return { attestations: out, files, warnings, notes, cleanup: async () => {
7703
+ await blame?.cleanup();
7704
+ } };
7705
+ }
7706
+
7707
+ // src/core/artifacts.ts
7708
+ var import_path4 = require("path");
7709
+ var MAX_ARTIFACT_FILES = 50;
7710
+ var TYPES = {
7711
+ ".zip": "application/zip",
7712
+ ".gz": "application/gzip",
7713
+ ".tgz": "application/gzip",
7714
+ ".tar": "application/x-tar",
7715
+ ".json": "application/json",
7716
+ ".xml": "application/xml",
7717
+ ".pdf": "application/pdf",
7718
+ ".txt": "text/plain",
7719
+ ".log": "text/plain",
7720
+ ".info": "text/plain",
7721
+ ".md": "text/markdown",
7722
+ ".csv": "text/csv",
7723
+ ".html": "text/html",
7724
+ ".htm": "text/html",
7725
+ ".png": "image/png",
7726
+ ".jpg": "image/jpeg",
7727
+ ".jpeg": "image/jpeg",
7728
+ ".svg": "image/svg+xml"
7729
+ };
7730
+ var contentTypeFor = (path) => TYPES[(0, import_path4.extname)(path).toLowerCase()] ?? "application/octet-stream";
7731
+ async function collectArtifacts(patterns, workspace, deps) {
7732
+ const warnings = [];
7733
+ if (!patterns.length) return { files: [], warnings };
7734
+ const find = deps.glob;
7735
+ const inside = [];
7736
+ for (const absolute of [...new Set(await find(patterns))]) {
7737
+ const checked = await checkWorkspacePath(workspace, absolute, { display: displayName(workspace, absolute) });
7738
+ if (!checked.ok) {
7739
+ if (checked.reason !== "missing") warnings.push(`${checked.message} and was not uploaded`);
7740
+ continue;
7741
+ }
7742
+ if (checked.file.path.toLowerCase().endsWith(BLAME_SIDECAR_SUFFIX)) {
7743
+ warnings.push(`${checked.file.path} ends in ${BLAME_SIDECAR_SUFFIX}, a name reserved for blame data, and was not uploaded`);
7744
+ continue;
7745
+ }
7746
+ inside.push({ absolute: checked.file.absolute, path: checked.file.path });
7747
+ }
7748
+ inside.sort((a, b) => a.path.localeCompare(b.path));
7749
+ if (inside.length > MAX_ARTIFACT_FILES) warnings.push(`${inside.length} files matched; only the first ${MAX_ARTIFACT_FILES} are uploaded`);
7750
+ const files = [];
7751
+ for (const f of inside.slice(0, MAX_ARTIFACT_FILES)) {
7752
+ const hashed = await hashWorkspaceFile(workspace, f.path);
7753
+ if (!hashed.ok) {
7754
+ if (hashed.reason !== "missing" && hashed.reason !== "not-file") warnings.push(`${hashed.message} and was not uploaded`);
7755
+ continue;
7756
+ }
7757
+ if (hashed.size === 0) continue;
7758
+ files.push({ path: f.path, absolute: f.absolute, workspace, size: hashed.size, sha256: hashed.sha256, contentType: contentTypeFor(f.path) });
7759
+ }
7760
+ return { files, warnings };
7023
7761
  }
7024
7762
 
7025
7763
  // src/core/request.ts
@@ -7092,23 +7830,34 @@ var sendReport = (baseUrl, provider, req, audience, deps, azure) => postJson(`${
7092
7830
  var completeReport = (baseUrl, provider, reportId, artifactIds, audience, deps, azure) => postJson(`${baseUrl}/api/ingest/${provider}/reports/${encodeURIComponent(reportId)}/complete`, azure ? { artifactIds, azure } : { artifactIds }, audience, deps);
7093
7831
 
7094
7832
  // src/core/upload.ts
7095
- var import_fs4 = require("fs");
7096
7833
  var http = __toESM(require("http"));
7097
7834
  var https = __toESM(require("https"));
7098
- function putOnce(slot, file) {
7099
- return new Promise((resolve3, reject) => {
7835
+ var refused = (message) => Object.assign(new Error(message), { retryable: false });
7836
+ async function openForUpload(file) {
7837
+ const opened = await openWorkspaceFile(file.workspace, file.source ?? file.path, { display: file.path });
7838
+ if (!opened.ok) throw refused(`${opened.message} and was not uploaded`);
7839
+ if (opened.size !== file.size) {
7840
+ await opened.handle.close();
7841
+ throw refused(`${file.path} changed since it was hashed and was not uploaded`);
7842
+ }
7843
+ return opened.handle;
7844
+ }
7845
+ async function putOnce(slot, file) {
7846
+ const handle = await openForUpload(file);
7847
+ const body = handle.createReadStream({ start: 0 });
7848
+ return new Promise((resolve4, reject) => {
7100
7849
  const url = new URL(slot.url);
7101
7850
  const mod = url.protocol === "http:" ? http : https;
7102
7851
  const headers = Object.fromEntries(Object.entries(slot.headers).filter(([k]) => !/^(proxy-)?authorization$|^cookie$/i.test(k)));
7103
7852
  const req = mod.request(url, { method: "PUT", headers, timeout: 10 * 6e4 }, (res) => {
7104
- let body = "";
7853
+ let body2 = "";
7105
7854
  res.setEncoding("utf8");
7106
7855
  res.on("data", (c) => {
7107
- if (body.length < 4e3) body += c;
7856
+ if (body2.length < 4e3) body2 += c;
7108
7857
  });
7109
7858
  res.on("end", () => {
7110
- if (res.statusCode && res.statusCode < 300) return resolve3();
7111
- const code = /<Code>([^<]+)<\/Code>/.exec(body)?.[1] ?? `HTTP ${res.statusCode}`;
7859
+ if (res.statusCode && res.statusCode < 300) return resolve4();
7860
+ const code = /<Code>([^<]+)<\/Code>/.exec(body2)?.[1] ?? `HTTP ${res.statusCode}`;
7112
7861
  const err = new Error(`${file.path}: upload refused (${code})`);
7113
7862
  err.retryable = (res.statusCode ?? 0) >= 500;
7114
7863
  reject(err);
@@ -7123,8 +7872,8 @@ function putOnce(slot, file) {
7123
7872
  if (e.retryable === void 0) e.retryable = true;
7124
7873
  reject(e);
7125
7874
  });
7126
- (0, import_fs4.createReadStream)(file.absolute).on("error", reject).pipe(req);
7127
- });
7875
+ body.on("error", reject).pipe(req);
7876
+ }).finally(() => body.destroy());
7128
7877
  }
7129
7878
  async function uploadFile(slot, file, attempts = 2) {
7130
7879
  for (let i = 1; ; i++) {
@@ -7243,7 +7992,9 @@ var OPTION_NAMES = {
7243
7992
  failOnError: { flag: "--fail-on-error", env: "PRODGATOR_FAIL_ON_ERROR", pipe: "FAIL_ON_ERROR" },
7244
7993
  tokenVariable: { flag: "--token-variable", env: "PRODGATOR_TOKEN_VARIABLE", pipe: null },
7245
7994
  gate: { flag: "--gate", env: "PRODGATOR_GATE", pipe: "GATE" },
7246
- gateTimeoutMinutes: { flag: "--gate-timeout", env: "PRODGATOR_GATE_TIMEOUT", pipe: "GATE_TIMEOUT" }
7995
+ gateTimeoutMinutes: { flag: "--gate-timeout", env: "PRODGATOR_GATE_TIMEOUT", pipe: "GATE_TIMEOUT" },
7996
+ // No pipe name: a Bitbucket pipe step sets PRODGATOR_BLAME: 'false' among its variables.
7997
+ blame: { flag: "--blame", env: "PRODGATOR_BLAME", pipe: null }
7247
7998
  };
7248
7999
  var DEFAULT_GATE_TIMEOUT_MINUTES = 60;
7249
8000
  var MAX_GATE_TIMEOUT_MINUTES = 720;
@@ -7275,7 +8026,11 @@ function parseOptions(argv, env) {
7275
8026
  pipe = true;
7276
8027
  continue;
7277
8028
  }
7278
- if ((a === "--gate" || a === "--allow-custom-url") && (argv[i + 1] === void 0 || argv[i + 1].startsWith("--"))) {
8029
+ if (a === "--no-blame") {
8030
+ flags.set("--blame", "false");
8031
+ continue;
8032
+ }
8033
+ if ((a === "--gate" || a === "--allow-custom-url" || a === "--blame") && (argv[i + 1] === void 0 || argv[i + 1].startsWith("--"))) {
7279
8034
  flags.set(a, "true");
7280
8035
  continue;
7281
8036
  }
@@ -7323,7 +8078,8 @@ function parseOptions(argv, env) {
7323
8078
  tokenVariable: get("tokenVariable")?.trim() ?? null,
7324
8079
  pipe,
7325
8080
  gate: (get("gate") ?? "false").trim().toLowerCase() === "true",
7326
- gateTimeoutMinutes: gateTimeout(get("gateTimeoutMinutes"))
8081
+ gateTimeoutMinutes: gateTimeout(get("gateTimeoutMinutes")),
8082
+ blame: (get("blame") ?? "true").trim().toLowerCase() !== "false"
7327
8083
  };
7328
8084
  }
7329
8085
 
@@ -7420,7 +8176,7 @@ function redactDeep(value, values) {
7420
8176
  }
7421
8177
 
7422
8178
  // src/cli/glob.ts
7423
- var import_path3 = require("path");
8179
+ var import_path5 = require("path");
7424
8180
  var { glob } = require_dist2();
7425
8181
  function workspaceGlob(workspace, warn) {
7426
8182
  return async (patterns) => {
@@ -7429,9 +8185,9 @@ function workspaceGlob(workspace, warn) {
7429
8185
  for (const raw of patterns) {
7430
8186
  const neg = raw.startsWith("!");
7431
8187
  let p = neg ? raw.slice(1) : raw;
7432
- if ((0, import_path3.isAbsolute)(p)) {
7433
- const rel = (0, import_path3.relative)(workspace, p);
7434
- if (!rel || rel.startsWith("..") || (0, import_path3.isAbsolute)(rel)) {
8188
+ if ((0, import_path5.isAbsolute)(p)) {
8189
+ const rel = (0, import_path5.relative)(workspace, p);
8190
+ if (!rel || rel.startsWith("..") || (0, import_path5.isAbsolute)(rel)) {
7435
8191
  warn(`${raw} is outside the workspace and was skipped`);
7436
8192
  continue;
7437
8193
  }
@@ -7441,7 +8197,7 @@ function workspaceGlob(workspace, warn) {
7441
8197
  }
7442
8198
  if (!include.length) return [];
7443
8199
  const found = await glob(include, { cwd: workspace, ignore, absolute: true, onlyFiles: true, dot: true, followSymbolicLinks: true });
7444
- return found.map((f) => (0, import_path3.resolve)(f)).sort();
8200
+ return found.map((f) => (0, import_path5.resolve)(f)).sort();
7445
8201
  };
7446
8202
  }
7447
8203
 
@@ -7504,16 +8260,17 @@ var SKIPPED = {
7504
8260
  monthly_quota: "your organization's monthly upload allowance is used up"
7505
8261
  };
7506
8262
  var DEFAULT_SUMMARY_FILE = "prodgator-summary.md";
7507
- async function readSummary(workspace, summary, file, info) {
8263
+ async function readSummary(workspace, summary, file, log) {
7508
8264
  if (summary) return summary;
7509
8265
  const path = file ?? DEFAULT_SUMMARY_FILE;
7510
- const absolute = (0, import_path4.isAbsolute)(path) ? path : (0, import_path4.join)(workspace, path);
7511
- try {
7512
- return await import_fs5.promises.readFile(absolute, "utf8");
7513
- } catch {
7514
- if (file) info(`No summary file at ${path}; sending the report without a summary.`);
7515
- return null;
8266
+ const read = await readWorkspaceText(workspace, path);
8267
+ if (read.ok) return read.text;
8268
+ if (read.reason === "missing") {
8269
+ if (file) log.info(`No summary file at ${path}; sending the report without a summary.`);
8270
+ } else {
8271
+ log.warn(`${read.message}; sending the report without a summary.`);
7516
8272
  }
8273
+ return null;
7517
8274
  }
7518
8275
  function mergeFiles(first, rest, warn) {
7519
8276
  const byPath = /* @__PURE__ */ new Map();
@@ -7527,10 +8284,11 @@ async function run(deps = {}) {
7527
8284
  const argv = deps.argv ?? process.argv.slice(2);
7528
8285
  const log = deps.log ?? { info: (m) => console.log(m), warn: (m) => console.log(`Warning: ${m}`), error: (m) => console.error(m) };
7529
8286
  if (argv.includes("--version")) {
7530
- log.info("3.19.0");
8287
+ log.info("3.25.0");
7531
8288
  return 0;
7532
8289
  }
7533
8290
  let failOnError = false;
8291
+ let cleanup = null;
7534
8292
  const truthy = (v) => v?.trim().toLowerCase() === "true";
7535
8293
  let gate = argv.some((a) => a === "--gate" || a === "--gate=true") || truthy(env.PRODGATOR_GATE) || argv.includes("--pipe") && truthy(env.GATE);
7536
8294
  try {
@@ -7545,7 +8303,7 @@ async function run(deps = {}) {
7545
8303
  const problem = tokenProblem(token, opts.audience, ci.provider, variable, (deps.nowMs ?? Date.now)());
7546
8304
  if (problem) throw new CliError(problem);
7547
8305
  if (opts.gate) {
7548
- const client2 = { getToken: async () => token, fetch: deps.fetch, sleep: deps.sleep, fixedToken: true, userAgent: `prodgator-report/${"3.19.0"} (${ci.provider}; gate)` };
8306
+ const client2 = { getToken: async () => token, fetch: deps.fetch, sleep: deps.sleep, fixedToken: true, userAgent: `prodgator-report/${"3.25.0"} (${ci.provider}; gate)` };
7549
8307
  return await runGate(ci, { prodgatorUrl: opts.prodgatorUrl, audience: opts.audience, timeoutMinutes: opts.gateTimeoutMinutes }, client2, {
7550
8308
  sleep: deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))),
7551
8309
  nowMs: deps.nowMs ?? Date.now,
@@ -7554,11 +8312,13 @@ async function run(deps = {}) {
7554
8312
  }
7555
8313
  const secrets = secretValues(env, [variable, ...opts.redactVariables]);
7556
8314
  const glob2 = workspaceGlob(ci.workspace, (w) => log.warn(w));
7557
- const rawSummary = await readSummary(ci.workspace, opts.summary, opts.summaryFile, (m) => log.info(m));
8315
+ const rawSummary = await readSummary(ci.workspace, opts.summary, opts.summaryFile, log);
7558
8316
  const summary = rawSummary ? truncateUtf8(redactText(rawSummary, secrets), MAX_SUMMARY_BYTES).text : null;
7559
- const scanned = await resolveAttestationFiles(parseAttestationsInput(opts.attestations, ci.workspace), ci.workspace);
8317
+ const scanned = await resolveAttestationFiles(await parseAttestationsInput(opts.attestations, ci.workspace), ci.workspace, { blame: opts.blame });
8318
+ cleanup = scanned.cleanup;
8319
+ scanned.notes.forEach((n) => log.info(n));
7560
8320
  const attestations = redactDeep(scanned.attestations, secrets);
7561
- const junit = await junitAttestation(opts.junit, { glob: glob2 });
8321
+ const junit = await junitAttestation(opts.junit, { glob: glob2, workspace: ci.workspace, warn: (w) => log.warn(w) });
7562
8322
  if (junit) attestations.push(junit);
7563
8323
  const globbed = await collectArtifacts(opts.artifacts, ci.workspace, { glob: glob2 });
7564
8324
  [...scanned.warnings, ...globbed.warnings].forEach((w) => log.warn(w));
@@ -7568,7 +8328,7 @@ async function run(deps = {}) {
7568
8328
  fetch: deps.fetch,
7569
8329
  sleep: deps.sleep,
7570
8330
  fixedToken: true,
7571
- userAgent: `prodgator-report/${"3.19.0"} (${ci.provider})`
8331
+ userAgent: `prodgator-report/${"3.25.0"} (${ci.provider})`
7572
8332
  };
7573
8333
  const request = buildIngestRequest({ job: { name: opts.jobName ?? ci.jobName, matrix: opts.matrix, runner: ci.runner }, name: opts.name, summary, attestations, files });
7574
8334
  const res = await sendReport(opts.prodgatorUrl, ci.provider, request, opts.audience, client, ci.azure);
@@ -7606,6 +8366,8 @@ async function run(deps = {}) {
7606
8366
  log.warn(`Prodgator report was not sent: ${msg2}`);
7607
8367
  deps.onOutcome?.("not_sent");
7608
8368
  return 0;
8369
+ } finally {
8370
+ await cleanup?.();
7609
8371
  }
7610
8372
  }
7611
8373