@prodgator/report 3.19.1 → 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 +793 -127
  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
  }
@@ -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);
@@ -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;
@@ -1657,9 +1657,9 @@ var require_picomatch = __commonJS({
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 });
@@ -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 fs2 = __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 || fs2
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 fs2 = 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 || fs2.readdir,
2611
- readdirSync: opts.fs.readdirSync || fs2.readdirSync,
2612
- realpath: opts.fs.realpath || fs2.realpath,
2613
- realpathSync: opts.fs.realpathSync || fs2.realpathSync,
2614
- stat: opts.fs.stat || fs2.stat,
2615
- statSync: opts.fs.statSync || fs2.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;
@@ -2626,12 +2626,12 @@ var require_dist2 = __commonJS({
2626
2626
  return patterns.length > 0 ? buildCrawler(options, patterns) : [];
2627
2627
  }
2628
2628
  async function glob2(globInput, options) {
2629
- const [crawler, relative3] = getCrawler(globInput, options);
2630
- return crawler ? formatPaths(await crawler.withPromise(), relative3) : [];
2629
+ const [crawler, relative4] = getCrawler(globInput, options);
2630
+ return crawler ? formatPaths(await crawler.withPromise(), relative4) : [];
2631
2631
  }
2632
2632
  function globSync(globInput, options) {
2633
- const [crawler, relative3] = getCrawler(globInput, options);
2634
- return crawler ? formatPaths(crawler.sync(), relative3) : [];
2633
+ const [crawler, relative4] = getCrawler(globInput, options);
2634
+ return crawler ? formatPaths(crawler.sync(), relative4) : [];
2635
2635
  }
2636
2636
  exports2.convertPathToPattern = convertPathToPattern;
2637
2637
  exports2.escapePath = escapePath;
@@ -3341,29 +3341,29 @@ var EntityDecoder = class {
3341
3341
  * @param {string} str
3342
3342
  * @returns {string}
3343
3343
  */
3344
- decode(str) {
3345
- if (typeof str !== "string" || str.length === 0) return str;
3346
- if (str.indexOf("&") === -1) return str;
3347
- 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;
3348
3348
  const chunks = [];
3349
- const len = str.length;
3349
+ const len = str2.length;
3350
3350
  let last = 0;
3351
3351
  let i = 0;
3352
3352
  const limitExpansions = this._maxTotalExpansions > 0;
3353
3353
  const limitLength = this._maxExpandedLength > 0;
3354
3354
  const checkLimits = limitExpansions || limitLength;
3355
3355
  while (i < len) {
3356
- if (str.charCodeAt(i) !== 38) {
3356
+ if (str2.charCodeAt(i) !== 38) {
3357
3357
  i++;
3358
3358
  continue;
3359
3359
  }
3360
3360
  let j = i + 1;
3361
- while (j < len && str.charCodeAt(j) !== 59 && j - i <= 32) j++;
3362
- 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) {
3363
3363
  i++;
3364
3364
  continue;
3365
3365
  }
3366
- const token = str.slice(i + 1, j);
3366
+ const token = str2.slice(i + 1, j);
3367
3367
  if (token.length === 0) {
3368
3368
  i++;
3369
3369
  continue;
@@ -3395,7 +3395,7 @@ var EntityDecoder = class {
3395
3395
  i++;
3396
3396
  continue;
3397
3397
  }
3398
- if (i > last) chunks.push(str.slice(last, i));
3398
+ if (i > last) chunks.push(str2.slice(last, i));
3399
3399
  chunks.push(replacement);
3400
3400
  last = j + 1;
3401
3401
  i = last;
@@ -3421,8 +3421,8 @@ var EntityDecoder = class {
3421
3421
  }
3422
3422
  }
3423
3423
  }
3424
- if (last < len) chunks.push(str.slice(last));
3425
- 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("");
3426
3426
  return this._postCheck(result, original);
3427
3427
  }
3428
3428
  // -------------------------------------------------------------------------
@@ -3741,7 +3741,7 @@ var getRegexes = (xmlVersion = "1.0", asciiOnly = false) => {
3741
3741
  if (asciiOnly) return regexesAscii;
3742
3742
  return xmlVersion === "1.1" ? regexes11 : regexes10;
3743
3743
  };
3744
- 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);
3745
3745
 
3746
3746
  // node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js
3747
3747
  var DocTypeReader = class {
@@ -4189,13 +4189,13 @@ var CHAR_0 = 48;
4189
4189
  var CHAR_9 = 57;
4190
4190
  var CHAR_MINUS = 45;
4191
4191
  var MINUS_SET = /* @__PURE__ */ new Set([8722, 65293, 65123]);
4192
- function anynum(str) {
4193
- if (typeof str !== "string") return str;
4194
- const len = str.length;
4195
- 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;
4196
4196
  let firstHit = -1;
4197
4197
  for (let i = 0; i < len; i++) {
4198
- const cc = str.charCodeAt(i);
4198
+ const cc = str2.charCodeAt(i);
4199
4199
  if (cc >= CHAR_0 && cc <= CHAR_9 || cc === CHAR_MINUS) continue;
4200
4200
  if (cc < TABLE_OFFSET) {
4201
4201
  if (MINUS_SET.has(cc)) {
@@ -4206,7 +4206,7 @@ function anynum(str) {
4206
4206
  }
4207
4207
  if (cc >= 55296 && cc <= 56319) {
4208
4208
  if (i + 1 < len) {
4209
- const low = str.charCodeAt(i + 1);
4209
+ const low = str2.charCodeAt(i + 1);
4210
4210
  if (low >= 56320 && low <= 57343) {
4211
4211
  const cp = 65536 + (cc - 55296 << 10) + (low - 56320);
4212
4212
  if (HIGH_MAP.has(cp)) {
@@ -4222,22 +4222,22 @@ function anynum(str) {
4222
4222
  break;
4223
4223
  }
4224
4224
  }
4225
- if (firstHit === -1) return str;
4225
+ if (firstHit === -1) return str2;
4226
4226
  const chars = [];
4227
- if (firstHit > 0) chars.push(str.slice(0, firstHit));
4227
+ if (firstHit > 0) chars.push(str2.slice(0, firstHit));
4228
4228
  for (let i = firstHit; i < len; i++) {
4229
- const cc = str.charCodeAt(i);
4229
+ const cc = str2.charCodeAt(i);
4230
4230
  if (cc >= CHAR_0 && cc <= CHAR_9 || cc === CHAR_MINUS) {
4231
- chars.push(str[i]);
4231
+ chars.push(str2[i]);
4232
4232
  continue;
4233
4233
  }
4234
4234
  if (cc < TABLE_OFFSET) {
4235
- chars.push(MINUS_SET.has(cc) ? "-" : str[i]);
4235
+ chars.push(MINUS_SET.has(cc) ? "-" : str2[i]);
4236
4236
  continue;
4237
4237
  }
4238
4238
  if (cc >= 55296 && cc <= 56319) {
4239
4239
  if (i + 1 < len) {
4240
- const low = str.charCodeAt(i + 1);
4240
+ const low = str2.charCodeAt(i + 1);
4241
4241
  if (low >= 56320 && low <= 57343) {
4242
4242
  const cp = 65536 + (cc - 55296 << 10) + (low - 56320);
4243
4243
  const d2 = HIGH_MAP.get(cp);
@@ -4248,7 +4248,7 @@ function anynum(str) {
4248
4248
  }
4249
4249
  }
4250
4250
  }
4251
- chars.push(str[i]);
4251
+ chars.push(str2[i]);
4252
4252
  continue;
4253
4253
  }
4254
4254
  if (MINUS_SET.has(cc)) {
@@ -4256,7 +4256,7 @@ function anynum(str) {
4256
4256
  continue;
4257
4257
  }
4258
4258
  const d = TABLE[cc - TABLE_OFFSET];
4259
- chars.push(d !== NOT_DIGIT ? String.fromCharCode(d + 48) : str[i]);
4259
+ chars.push(d !== NOT_DIGIT ? String.fromCharCode(d + 48) : str2[i]);
4260
4260
  }
4261
4261
  return chars.join("");
4262
4262
  }
@@ -4279,12 +4279,12 @@ var consider = {
4279
4279
  // "null", "infinity" (Infinity type), "string" ("Infinity" (the string literal))
4280
4280
  unicode: false
4281
4281
  };
4282
- function toNumber(str, options = {}) {
4282
+ function toNumber(str2, options = {}) {
4283
4283
  options = Object.assign({}, consider, options);
4284
- if (!str || typeof str !== "string") return str;
4285
- let trimmedStr = str.trim();
4286
- if (trimmedStr.length === 0) return str;
4287
- 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;
4288
4288
  else if (trimmedStr === "0") return 0;
4289
4289
  if (options.unicode) {
4290
4290
  trimmedStr = anynum_default(trimmedStr);
@@ -4297,9 +4297,9 @@ function toNumber(str, options = {}) {
4297
4297
  } else if (options.octal && octRegex.test(trimmedStr)) {
4298
4298
  return parse_int(trimmedStr, 8);
4299
4299
  } else if (!isFinite(trimmedStr)) {
4300
- return handleInfinity(str, Number(trimmedStr), options);
4300
+ return handleInfinity(str2, Number(trimmedStr), options);
4301
4301
  } else if (trimmedStr.includes("e") || trimmedStr.includes("E")) {
4302
- return resolveEnotation(str, trimmedStr, options);
4302
+ return resolveEnotation(str2, trimmedStr, options);
4303
4303
  } else {
4304
4304
  const match = numRegex.exec(trimmedStr);
4305
4305
  if (match) {
@@ -4308,38 +4308,38 @@ function toNumber(str, options = {}) {
4308
4308
  let numTrimmedByZeros = trimZeros(match[3]);
4309
4309
  const decimalAdjacentToLeadingZeros = sign ? (
4310
4310
  // 0., -00., 000.
4311
- str[leadingZeros.length + 1] === "."
4312
- ) : str[leadingZeros.length] === ".";
4311
+ str2[leadingZeros.length + 1] === "."
4312
+ ) : str2[leadingZeros.length] === ".";
4313
4313
  if (!options.leadingZeros && (leadingZeros.length > 1 || leadingZeros.length === 1 && !decimalAdjacentToLeadingZeros)) {
4314
- return str;
4314
+ return str2;
4315
4315
  } else {
4316
4316
  const num = Number(trimmedStr);
4317
4317
  const parsedStr = String(num);
4318
4318
  if (num === 0) return num;
4319
4319
  if (parsedStr.search(/[eE]/) !== -1) {
4320
4320
  if (options.eNotation) return num;
4321
- else return str;
4321
+ else return str2;
4322
4322
  } else if (trimmedStr.indexOf(".") !== -1) {
4323
4323
  if (parsedStr === "0") return num;
4324
4324
  else if (parsedStr === numTrimmedByZeros) return num;
4325
4325
  else if (parsedStr === `${sign}${numTrimmedByZeros}`) return num;
4326
- else return str;
4326
+ else return str2;
4327
4327
  }
4328
4328
  let n = leadingZeros ? numTrimmedByZeros : trimmedStr;
4329
4329
  if (leadingZeros) {
4330
- return n === parsedStr || sign + n === parsedStr ? num : str;
4330
+ return n === parsedStr || sign + n === parsedStr ? num : str2;
4331
4331
  } else {
4332
- return n === parsedStr || n === sign + parsedStr ? num : str;
4332
+ return n === parsedStr || n === sign + parsedStr ? num : str2;
4333
4333
  }
4334
4334
  }
4335
4335
  } else {
4336
- return str;
4336
+ return str2;
4337
4337
  }
4338
4338
  }
4339
4339
  }
4340
4340
  var eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/;
4341
- function resolveEnotation(str, trimmedStr, options) {
4342
- if (!options.eNotation) return str;
4341
+ function resolveEnotation(str2, trimmedStr, options) {
4342
+ if (!options.eNotation) return str2;
4343
4343
  const notation = trimmedStr.match(eNotationRegx);
4344
4344
  if (notation) {
4345
4345
  let sign = notation[1] || "";
@@ -4347,21 +4347,21 @@ function resolveEnotation(str, trimmedStr, options) {
4347
4347
  const leadingZeros = notation[2];
4348
4348
  const eAdjacentToLeadingZeros = sign ? (
4349
4349
  // 0E.
4350
- str[leadingZeros.length + 1] === eChar
4351
- ) : str[leadingZeros.length] === eChar;
4352
- 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;
4353
4353
  else if (leadingZeros.length === 1 && (notation[3].startsWith(`.${eChar}`) || notation[3][0] === eChar)) {
4354
4354
  return Number(trimmedStr);
4355
4355
  } else if (leadingZeros.length > 0) {
4356
4356
  if (options.leadingZeros && !eAdjacentToLeadingZeros) {
4357
4357
  trimmedStr = (notation[1] || "") + notation[3];
4358
4358
  return Number(trimmedStr);
4359
- } else return str;
4359
+ } else return str2;
4360
4360
  } else {
4361
4361
  return Number(trimmedStr);
4362
4362
  }
4363
4363
  } else {
4364
- return str;
4364
+ return str2;
4365
4365
  }
4366
4366
  }
4367
4367
  function trimZeros(numStr) {
@@ -4377,14 +4377,14 @@ function trimZeros(numStr) {
4377
4377
  return numStr;
4378
4378
  }
4379
4379
  function parse_int(numStr, base) {
4380
- const str = numStr.trim();
4381
- if (base === 2 || base === 8) numStr = str.substring(2);
4380
+ const str2 = numStr.trim();
4381
+ if (base === 2 || base === 8) numStr = str2.substring(2);
4382
4382
  if (parseInt) return parseInt(numStr, base);
4383
4383
  else if (Number.parseInt) return Number.parseInt(numStr, base);
4384
4384
  else if (window && window.parseInt) return window.parseInt(numStr, base);
4385
4385
  else throw new Error("parseInt, Number.parseInt, window.parseInt are not supported");
4386
4386
  }
4387
- function handleInfinity(str, num, options) {
4387
+ function handleInfinity(str2, num, options) {
4388
4388
  const isPositive = num === Infinity;
4389
4389
  switch (options.infinity.toLowerCase()) {
4390
4390
  case "null":
@@ -4396,7 +4396,7 @@ function handleInfinity(str, num, options) {
4396
4396
  return isPositive ? "Infinity" : "-Infinity";
4397
4397
  case "original":
4398
4398
  default:
4399
- return str;
4399
+ return str2;
4400
4400
  }
4401
4401
  }
4402
4402
 
@@ -5080,21 +5080,21 @@ var Matcher = class {
5080
5080
  * @returns {string}
5081
5081
  */
5082
5082
  toString(separator, includeNamespace = true) {
5083
- const sep3 = separator || this.separator;
5084
- const isDefault = sep3 === this.separator && includeNamespace === true;
5083
+ const sep4 = separator || this.separator;
5084
+ const isDefault = sep4 === this.separator && includeNamespace === true;
5085
5085
  if (isDefault) {
5086
5086
  if (this._pathStringCache !== null) {
5087
5087
  return this._pathStringCache;
5088
5088
  }
5089
5089
  const result = this.path.map(
5090
5090
  (n) => n.namespace ? `${n.namespace}:${n.tag}` : n.tag
5091
- ).join(sep3);
5091
+ ).join(sep4);
5092
5092
  this._pathStringCache = result;
5093
5093
  return result;
5094
5094
  }
5095
5095
  return this.path.map(
5096
5096
  (n) => includeNamespace && n.namespace ? `${n.namespace}:${n.tag}` : n.tag
5097
- ).join(sep3);
5097
+ ).join(sep4);
5098
5098
  }
5099
5099
  /**
5100
5100
  * Get path as array of tag names.
@@ -6478,12 +6478,12 @@ function tagExpWithClosingIndex(xmlData, i, closingChar = ">") {
6478
6478
  }
6479
6479
  }
6480
6480
  }
6481
- function findClosingIndex(xmlData, str, i, errMsg) {
6482
- const closingIndex = xmlData.indexOf(str, i);
6481
+ function findClosingIndex(xmlData, str2, i, errMsg) {
6482
+ const closingIndex = xmlData.indexOf(str2, i);
6483
6483
  if (closingIndex === -1) {
6484
6484
  throw new Error(errMsg);
6485
6485
  } else {
6486
- return closingIndex + str.length - 1;
6486
+ return closingIndex + str2.length - 1;
6487
6487
  }
6488
6488
  }
6489
6489
  function findClosingChar(xmlData, char, i, errMsg) {
@@ -6614,11 +6614,11 @@ function stripAttributePrefix(attrs, prefix) {
6614
6614
  function prettify(node, options, matcher, readonlyMatcher) {
6615
6615
  return compress(node, options, matcher, readonlyMatcher);
6616
6616
  }
6617
- function compress(arr, options, matcher, readonlyMatcher) {
6617
+ function compress(arr2, options, matcher, readonlyMatcher) {
6618
6618
  let text;
6619
6619
  const compressedObj = {};
6620
- for (let i = 0; i < arr.length; i++) {
6621
- const tagObj = arr[i];
6620
+ for (let i = 0; i < arr2.length; i++) {
6621
+ const tagObj = arr2[i];
6622
6622
  const property = propName(tagObj);
6623
6623
  if (property !== void 0 && property !== options.textNodeName) {
6624
6624
  const rawAttrs = stripAttributePrefix(
@@ -6935,8 +6935,568 @@ async function junitAttestation(patterns, opts) {
6935
6935
  }
6936
6936
 
6937
6937
  // src/core/attestations.ts
6938
+ var import_crypto3 = require("crypto");
6939
+ var import_fs3 = require("fs");
6940
+ var import_os = require("os");
6941
+ var import_path3 = require("path");
6942
+
6943
+ // src/core/blame.ts
6944
+ var import_child_process = require("child_process");
6938
6945
  var import_crypto2 = require("crypto");
6946
+ var import_fs2 = require("fs");
6939
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
+ }
6972
+ };
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));
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 };
7024
+ }
7025
+ function findGit(workspace, o = {}) {
7026
+ return searchGit(workspace, o).git;
7027
+ }
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 });
7296
+ continue;
7297
+ }
7298
+ if (!current) continue;
7299
+ if (raw.startsWith(" ")) {
7300
+ lines.push(raw.slice(1));
7301
+ order.push(current);
7302
+ continue;
7303
+ }
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;
7307
+ }
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;
7336
+ }
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
+ };
7497
+ }
7498
+
7499
+ // src/core/attestations.ts
6940
7500
  var KINDS = ["test-results", "coverage", "sbom", "scan", "custom", "provenance"];
6941
7501
  async function parseAttestationsInput(value, workspace) {
6942
7502
  if (!value) return [];
@@ -6967,7 +7527,7 @@ function contentTypeForScanFile(format, path) {
6967
7527
  if (format === "sarif") return "application/sarif+json";
6968
7528
  if (format === "cyclonedx") return "application/vnd.cyclonedx+json";
6969
7529
  if (format === "spdx") return "application/spdx+json";
6970
- 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";
6971
7531
  }
6972
7532
  function sarifCounts(json) {
6973
7533
  const counts = { critical: 0, high: 0, medium: 0, low: 0 };
@@ -6998,12 +7558,77 @@ function sarifCounts(json) {
6998
7558
  }
6999
7559
  return counts;
7000
7560
  }
7001
- async function resolveAttestationFiles(attestations, workspace) {
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
+ };
7611
+ async function resolveAttestationFiles(attestations, workspace, deps = {}) {
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) {
7002
7621
  const files = [];
7003
7622
  const warnings = [];
7004
7623
  const out = [];
7624
+ let warnedTooLargeForBlame = false;
7005
7625
  for (const input of attestations) {
7006
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
+ }
7007
7632
  if (a.kind === "scan" && "mode" in a.data && a.data.mode !== "full" && a.data.mode !== "diff") {
7008
7633
  const { mode: _ignored, ...rest } = a.data;
7009
7634
  a = { ...a, data: rest };
@@ -7018,6 +7643,7 @@ async function resolveAttestationFiles(attestations, workspace) {
7018
7643
  const scanFormat = typeof format === "string" && SCAN_FILE_FORMATS.includes(format) ? format : void 0;
7019
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;
7020
7645
  const notAttached = `and was not attached to "${a.name}"`;
7646
+ const wantBlame = !!blame && a.kind === "scan" && scanFormat === "sarif";
7021
7647
  const opened = await openWorkspaceFile(workspace, file);
7022
7648
  if (!opened.ok) {
7023
7649
  const missing = opened.reason === "missing" || opened.reason === "not-file";
@@ -7025,18 +7651,26 @@ async function resolveAttestationFiles(attestations, workspace) {
7025
7651
  out.push(a);
7026
7652
  continue;
7027
7653
  }
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}`);
7657
+ out.push(a);
7658
+ continue;
7659
+ }
7028
7660
  let sha256;
7029
7661
  let counts = null;
7662
+ let sarifBytes = null;
7030
7663
  try {
7031
7664
  if (opened.size === 0) {
7032
7665
  warnings.push(`${file} was not found (or is empty) ${notAttached}`);
7033
7666
  out.push(a);
7034
7667
  continue;
7035
7668
  }
7036
- if (wantCounts && opened.size <= MAX_SARIF_BYTES) {
7669
+ if ((wantCounts || wantBlame) && opened.size <= MAX_SARIF_BYTES) {
7037
7670
  const bytes = await opened.handle.readFile();
7038
- sha256 = (0, import_crypto2.createHash)("sha256").update(bytes).digest("hex");
7039
- counts = sarifCounts(bytes.toString("utf8"));
7671
+ sha256 = (0, import_crypto3.createHash)("sha256").update(bytes).digest("hex");
7672
+ if (wantCounts) counts = sarifCounts(bytes.toString("utf8"));
7673
+ if (wantBlame) sarifBytes = bytes;
7040
7674
  } else {
7041
7675
  sha256 = await sha256Handle(opened.handle);
7042
7676
  }
@@ -7049,13 +7683,29 @@ async function resolveAttestationFiles(attestations, workspace) {
7049
7683
  if (a.kind === "scan" && scanFormat) data.format = scanFormat;
7050
7684
  if (counts) Object.assign(data, counts);
7051
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;
7697
+ }
7698
+ }
7052
7699
  out.push({ kind: a.kind, name: a.name, status: a.status, data });
7053
7700
  }
7054
- 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
+ } };
7055
7705
  }
7056
7706
 
7057
7707
  // src/core/artifacts.ts
7058
- var import_path3 = require("path");
7708
+ var import_path4 = require("path");
7059
7709
  var MAX_ARTIFACT_FILES = 50;
7060
7710
  var TYPES = {
7061
7711
  ".zip": "application/zip",
@@ -7077,7 +7727,7 @@ var TYPES = {
7077
7727
  ".jpeg": "image/jpeg",
7078
7728
  ".svg": "image/svg+xml"
7079
7729
  };
7080
- var contentTypeFor = (path) => TYPES[(0, import_path3.extname)(path).toLowerCase()] ?? "application/octet-stream";
7730
+ var contentTypeFor = (path) => TYPES[(0, import_path4.extname)(path).toLowerCase()] ?? "application/octet-stream";
7081
7731
  async function collectArtifacts(patterns, workspace, deps) {
7082
7732
  const warnings = [];
7083
7733
  if (!patterns.length) return { files: [], warnings };
@@ -7089,6 +7739,10 @@ async function collectArtifacts(patterns, workspace, deps) {
7089
7739
  if (checked.reason !== "missing") warnings.push(`${checked.message} and was not uploaded`);
7090
7740
  continue;
7091
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
+ }
7092
7746
  inside.push({ absolute: checked.file.absolute, path: checked.file.path });
7093
7747
  }
7094
7748
  inside.sort((a, b) => a.path.localeCompare(b.path));
@@ -7180,7 +7834,7 @@ var http = __toESM(require("http"));
7180
7834
  var https = __toESM(require("https"));
7181
7835
  var refused = (message) => Object.assign(new Error(message), { retryable: false });
7182
7836
  async function openForUpload(file) {
7183
- const opened = await openWorkspaceFile(file.workspace, file.path);
7837
+ const opened = await openWorkspaceFile(file.workspace, file.source ?? file.path, { display: file.path });
7184
7838
  if (!opened.ok) throw refused(`${opened.message} and was not uploaded`);
7185
7839
  if (opened.size !== file.size) {
7186
7840
  await opened.handle.close();
@@ -7191,7 +7845,7 @@ async function openForUpload(file) {
7191
7845
  async function putOnce(slot, file) {
7192
7846
  const handle = await openForUpload(file);
7193
7847
  const body = handle.createReadStream({ start: 0 });
7194
- return new Promise((resolve3, reject) => {
7848
+ return new Promise((resolve4, reject) => {
7195
7849
  const url = new URL(slot.url);
7196
7850
  const mod = url.protocol === "http:" ? http : https;
7197
7851
  const headers = Object.fromEntries(Object.entries(slot.headers).filter(([k]) => !/^(proxy-)?authorization$|^cookie$/i.test(k)));
@@ -7202,7 +7856,7 @@ async function putOnce(slot, file) {
7202
7856
  if (body2.length < 4e3) body2 += c;
7203
7857
  });
7204
7858
  res.on("end", () => {
7205
- if (res.statusCode && res.statusCode < 300) return resolve3();
7859
+ if (res.statusCode && res.statusCode < 300) return resolve4();
7206
7860
  const code = /<Code>([^<]+)<\/Code>/.exec(body2)?.[1] ?? `HTTP ${res.statusCode}`;
7207
7861
  const err = new Error(`${file.path}: upload refused (${code})`);
7208
7862
  err.retryable = (res.statusCode ?? 0) >= 500;
@@ -7338,7 +7992,9 @@ var OPTION_NAMES = {
7338
7992
  failOnError: { flag: "--fail-on-error", env: "PRODGATOR_FAIL_ON_ERROR", pipe: "FAIL_ON_ERROR" },
7339
7993
  tokenVariable: { flag: "--token-variable", env: "PRODGATOR_TOKEN_VARIABLE", pipe: null },
7340
7994
  gate: { flag: "--gate", env: "PRODGATOR_GATE", pipe: "GATE" },
7341
- 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 }
7342
7998
  };
7343
7999
  var DEFAULT_GATE_TIMEOUT_MINUTES = 60;
7344
8000
  var MAX_GATE_TIMEOUT_MINUTES = 720;
@@ -7370,7 +8026,11 @@ function parseOptions(argv, env) {
7370
8026
  pipe = true;
7371
8027
  continue;
7372
8028
  }
7373
- 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("--"))) {
7374
8034
  flags.set(a, "true");
7375
8035
  continue;
7376
8036
  }
@@ -7418,7 +8078,8 @@ function parseOptions(argv, env) {
7418
8078
  tokenVariable: get("tokenVariable")?.trim() ?? null,
7419
8079
  pipe,
7420
8080
  gate: (get("gate") ?? "false").trim().toLowerCase() === "true",
7421
- gateTimeoutMinutes: gateTimeout(get("gateTimeoutMinutes"))
8081
+ gateTimeoutMinutes: gateTimeout(get("gateTimeoutMinutes")),
8082
+ blame: (get("blame") ?? "true").trim().toLowerCase() !== "false"
7422
8083
  };
7423
8084
  }
7424
8085
 
@@ -7515,7 +8176,7 @@ function redactDeep(value, values) {
7515
8176
  }
7516
8177
 
7517
8178
  // src/cli/glob.ts
7518
- var import_path4 = require("path");
8179
+ var import_path5 = require("path");
7519
8180
  var { glob } = require_dist2();
7520
8181
  function workspaceGlob(workspace, warn) {
7521
8182
  return async (patterns) => {
@@ -7524,9 +8185,9 @@ function workspaceGlob(workspace, warn) {
7524
8185
  for (const raw of patterns) {
7525
8186
  const neg = raw.startsWith("!");
7526
8187
  let p = neg ? raw.slice(1) : raw;
7527
- if ((0, import_path4.isAbsolute)(p)) {
7528
- const rel = (0, import_path4.relative)(workspace, p);
7529
- if (!rel || rel.startsWith("..") || (0, import_path4.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)) {
7530
8191
  warn(`${raw} is outside the workspace and was skipped`);
7531
8192
  continue;
7532
8193
  }
@@ -7536,7 +8197,7 @@ function workspaceGlob(workspace, warn) {
7536
8197
  }
7537
8198
  if (!include.length) return [];
7538
8199
  const found = await glob(include, { cwd: workspace, ignore, absolute: true, onlyFiles: true, dot: true, followSymbolicLinks: true });
7539
- return found.map((f) => (0, import_path4.resolve)(f)).sort();
8200
+ return found.map((f) => (0, import_path5.resolve)(f)).sort();
7540
8201
  };
7541
8202
  }
7542
8203
 
@@ -7623,10 +8284,11 @@ async function run(deps = {}) {
7623
8284
  const argv = deps.argv ?? process.argv.slice(2);
7624
8285
  const log = deps.log ?? { info: (m) => console.log(m), warn: (m) => console.log(`Warning: ${m}`), error: (m) => console.error(m) };
7625
8286
  if (argv.includes("--version")) {
7626
- log.info("3.19.1");
8287
+ log.info("3.25.0");
7627
8288
  return 0;
7628
8289
  }
7629
8290
  let failOnError = false;
8291
+ let cleanup = null;
7630
8292
  const truthy = (v) => v?.trim().toLowerCase() === "true";
7631
8293
  let gate = argv.some((a) => a === "--gate" || a === "--gate=true") || truthy(env.PRODGATOR_GATE) || argv.includes("--pipe") && truthy(env.GATE);
7632
8294
  try {
@@ -7641,7 +8303,7 @@ async function run(deps = {}) {
7641
8303
  const problem = tokenProblem(token, opts.audience, ci.provider, variable, (deps.nowMs ?? Date.now)());
7642
8304
  if (problem) throw new CliError(problem);
7643
8305
  if (opts.gate) {
7644
- const client2 = { getToken: async () => token, fetch: deps.fetch, sleep: deps.sleep, fixedToken: true, userAgent: `prodgator-report/${"3.19.1"} (${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)` };
7645
8307
  return await runGate(ci, { prodgatorUrl: opts.prodgatorUrl, audience: opts.audience, timeoutMinutes: opts.gateTimeoutMinutes }, client2, {
7646
8308
  sleep: deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))),
7647
8309
  nowMs: deps.nowMs ?? Date.now,
@@ -7652,7 +8314,9 @@ async function run(deps = {}) {
7652
8314
  const glob2 = workspaceGlob(ci.workspace, (w) => log.warn(w));
7653
8315
  const rawSummary = await readSummary(ci.workspace, opts.summary, opts.summaryFile, log);
7654
8316
  const summary = rawSummary ? truncateUtf8(redactText(rawSummary, secrets), MAX_SUMMARY_BYTES).text : null;
7655
- const scanned = await resolveAttestationFiles(await 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));
7656
8320
  const attestations = redactDeep(scanned.attestations, secrets);
7657
8321
  const junit = await junitAttestation(opts.junit, { glob: glob2, workspace: ci.workspace, warn: (w) => log.warn(w) });
7658
8322
  if (junit) attestations.push(junit);
@@ -7664,7 +8328,7 @@ async function run(deps = {}) {
7664
8328
  fetch: deps.fetch,
7665
8329
  sleep: deps.sleep,
7666
8330
  fixedToken: true,
7667
- userAgent: `prodgator-report/${"3.19.1"} (${ci.provider})`
8331
+ userAgent: `prodgator-report/${"3.25.0"} (${ci.provider})`
7668
8332
  };
7669
8333
  const request = buildIngestRequest({ job: { name: opts.jobName ?? ci.jobName, matrix: opts.matrix, runner: ci.runner }, name: opts.name, summary, attestations, files });
7670
8334
  const res = await sendReport(opts.prodgatorUrl, ci.provider, request, opts.audience, client, ci.azure);
@@ -7702,6 +8366,8 @@ async function run(deps = {}) {
7702
8366
  log.warn(`Prodgator report was not sent: ${msg2}`);
7703
8367
  deps.onOutcome?.("not_sent");
7704
8368
  return 0;
8369
+ } finally {
8370
+ await cleanup?.();
7705
8371
  }
7706
8372
  }
7707
8373