@prodgator/report 3.19.0 → 3.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +257 -161
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -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
|
|
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
|
-
} =
|
|
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 :
|
|
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 =
|
|
901
|
-
const EXTGLOB_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,
|
|
@@ -1594,7 +1594,7 @@ var require_parse = __commonJS({
|
|
|
1594
1594
|
NO_DOTS_SLASH,
|
|
1595
1595
|
STAR,
|
|
1596
1596
|
START_ANCHOR
|
|
1597
|
-
} =
|
|
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 ? "" : "?:";
|
|
@@ -1652,7 +1652,7 @@ var require_picomatch = __commonJS({
|
|
|
1652
1652
|
var scan = require_scan();
|
|
1653
1653
|
var parse = require_parse();
|
|
1654
1654
|
var utils = require_utils();
|
|
1655
|
-
var
|
|
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)) {
|
|
@@ -1780,7 +1780,7 @@ var require_picomatch = __commonJS({
|
|
|
1780
1780
|
return /$^/;
|
|
1781
1781
|
}
|
|
1782
1782
|
};
|
|
1783
|
-
picomatch.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
|
|
1829
|
+
var fs2 = __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 ||
|
|
2126
|
+
fs: options.fs || fs2
|
|
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
|
|
2361
|
+
var fs2 = 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 ||
|
|
2611
|
-
readdirSync: opts.fs.readdirSync ||
|
|
2612
|
-
realpath: opts.fs.realpath ||
|
|
2613
|
-
realpathSync: opts.fs.realpathSync ||
|
|
2614
|
-
stat: opts.fs.stat ||
|
|
2615
|
-
statSync: opts.fs.statSync ||
|
|
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
|
|
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,
|
|
2630
|
-
return crawler ? formatPaths(await crawler.withPromise(),
|
|
2629
|
+
const [crawler, relative3] = getCrawler(globInput, options);
|
|
2630
|
+
return crawler ? formatPaths(await crawler.withPromise(), relative3) : [];
|
|
2631
2631
|
}
|
|
2632
2632
|
function globSync(globInput, options) {
|
|
2633
|
-
const [crawler,
|
|
2634
|
-
return crawler ? formatPaths(crawler.sync(),
|
|
2633
|
+
const [crawler, relative3] = getCrawler(globInput, options);
|
|
2634
|
+
return crawler ? formatPaths(crawler.sync(), relative3) : [];
|
|
2635
2635
|
}
|
|
2636
2636
|
exports2.convertPathToPattern = convertPathToPattern;
|
|
2637
2637
|
exports2.escapePath = escapePath;
|
|
@@ -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";
|
|
@@ -5087,21 +5080,21 @@ var Matcher = class {
|
|
|
5087
5080
|
* @returns {string}
|
|
5088
5081
|
*/
|
|
5089
5082
|
toString(separator, includeNamespace = true) {
|
|
5090
|
-
const
|
|
5091
|
-
const isDefault =
|
|
5083
|
+
const sep3 = separator || this.separator;
|
|
5084
|
+
const isDefault = sep3 === this.separator && includeNamespace === true;
|
|
5092
5085
|
if (isDefault) {
|
|
5093
5086
|
if (this._pathStringCache !== null) {
|
|
5094
5087
|
return this._pathStringCache;
|
|
5095
5088
|
}
|
|
5096
5089
|
const result = this.path.map(
|
|
5097
5090
|
(n) => n.namespace ? `${n.namespace}:${n.tag}` : n.tag
|
|
5098
|
-
).join(
|
|
5091
|
+
).join(sep3);
|
|
5099
5092
|
this._pathStringCache = result;
|
|
5100
5093
|
return result;
|
|
5101
5094
|
}
|
|
5102
5095
|
return this.path.map(
|
|
5103
5096
|
(n) => includeNamespace && n.namespace ? `${n.namespace}:${n.tag}` : n.tag
|
|
5104
|
-
).join(
|
|
5097
|
+
).join(sep3);
|
|
5105
5098
|
}
|
|
5106
5099
|
/**
|
|
5107
5100
|
* Get path as array of tag names.
|
|
@@ -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
|
|
6829
|
-
if (
|
|
6830
|
-
|
|
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,22 @@ 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
|
|
6842
|
-
var
|
|
6938
|
+
var import_crypto2 = require("crypto");
|
|
6843
6939
|
var import_path2 = require("path");
|
|
6844
|
-
|
|
6845
|
-
// src/core/artifacts.ts
|
|
6846
|
-
var import_crypto = require("crypto");
|
|
6847
|
-
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"
|
|
6869
|
-
};
|
|
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")));
|
|
6875
|
-
});
|
|
6876
|
-
}
|
|
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;
|
|
6883
|
-
}
|
|
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`);
|
|
6893
|
-
continue;
|
|
6894
|
-
}
|
|
6895
|
-
const path = rel.split(import_path.sep).join("/");
|
|
6896
|
-
const refused = await refusalFor(absolute, path, workspace);
|
|
6897
|
-
if (refused) {
|
|
6898
|
-
warnings.push(refused);
|
|
6899
|
-
continue;
|
|
6900
|
-
}
|
|
6901
|
-
inside.push({ absolute, path });
|
|
6902
|
-
}
|
|
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) });
|
|
6910
|
-
}
|
|
6911
|
-
return { files, warnings };
|
|
6912
|
-
}
|
|
6913
|
-
|
|
6914
|
-
// src/core/attestations.ts
|
|
6915
6940
|
var KINDS = ["test-results", "coverage", "sbom", "scan", "custom", "provenance"];
|
|
6916
|
-
function parseAttestationsInput(value, workspace) {
|
|
6941
|
+
async function parseAttestationsInput(value, workspace) {
|
|
6917
6942
|
if (!value) return [];
|
|
6918
|
-
|
|
6943
|
+
let text = value;
|
|
6944
|
+
if (!/^\s*[[{]/.test(value)) {
|
|
6945
|
+
const read = await readWorkspaceText(workspace, value.trim());
|
|
6946
|
+
if (!read.ok) throw new Error(`attestations file ${read.message}; it was not read`);
|
|
6947
|
+
text = read.text ?? "";
|
|
6948
|
+
}
|
|
6919
6949
|
let parsed;
|
|
6920
6950
|
try {
|
|
6921
6951
|
parsed = JSON.parse(text);
|
|
@@ -6968,8 +6998,7 @@ function sarifCounts(json) {
|
|
|
6968
6998
|
}
|
|
6969
6999
|
return counts;
|
|
6970
7000
|
}
|
|
6971
|
-
async function resolveAttestationFiles(attestations, workspace
|
|
6972
|
-
const hash = deps.sha256File ?? sha256File;
|
|
7001
|
+
async function resolveAttestationFiles(attestations, workspace) {
|
|
6973
7002
|
const files = [];
|
|
6974
7003
|
const warnings = [];
|
|
6975
7004
|
const out = [];
|
|
@@ -6987,41 +7016,96 @@ async function resolveAttestationFiles(attestations, workspace, deps = {}) {
|
|
|
6987
7016
|
}
|
|
6988
7017
|
const format = a.format;
|
|
6989
7018
|
const scanFormat = typeof format === "string" && SCAN_FILE_FORMATS.includes(format) ? format : void 0;
|
|
6990
|
-
const
|
|
6991
|
-
const
|
|
6992
|
-
|
|
6993
|
-
|
|
7019
|
+
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
|
+
const notAttached = `and was not attached to "${a.name}"`;
|
|
7021
|
+
const opened = await openWorkspaceFile(workspace, file);
|
|
7022
|
+
if (!opened.ok) {
|
|
7023
|
+
const missing = opened.reason === "missing" || opened.reason === "not-file";
|
|
7024
|
+
warnings.push(missing ? `${file} was not found (or is empty) ${notAttached}` : `${opened.message} ${notAttached}`);
|
|
6994
7025
|
out.push(a);
|
|
6995
7026
|
continue;
|
|
6996
7027
|
}
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
}
|
|
7010
|
-
files.push({ path, absolute, size: stat.size, sha256: await hash(absolute), contentType: contentTypeForScanFile(scanFormat, path) });
|
|
7011
|
-
const data = { ...a.data, artifact: path };
|
|
7012
|
-
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")));
|
|
7028
|
+
let sha256;
|
|
7029
|
+
let counts = null;
|
|
7030
|
+
try {
|
|
7031
|
+
if (opened.size === 0) {
|
|
7032
|
+
warnings.push(`${file} was not found (or is empty) ${notAttached}`);
|
|
7033
|
+
out.push(a);
|
|
7034
|
+
continue;
|
|
7035
|
+
}
|
|
7036
|
+
if (wantCounts && opened.size <= MAX_SARIF_BYTES) {
|
|
7037
|
+
const bytes = await opened.handle.readFile();
|
|
7038
|
+
sha256 = (0, import_crypto2.createHash)("sha256").update(bytes).digest("hex");
|
|
7039
|
+
counts = sarifCounts(bytes.toString("utf8"));
|
|
7016
7040
|
} else {
|
|
7017
|
-
|
|
7041
|
+
sha256 = await sha256Handle(opened.handle);
|
|
7018
7042
|
}
|
|
7043
|
+
} finally {
|
|
7044
|
+
await opened.handle.close();
|
|
7019
7045
|
}
|
|
7046
|
+
const { path, absolute } = opened.file;
|
|
7047
|
+
files.push({ path, absolute, workspace, size: opened.size, sha256, contentType: contentTypeForScanFile(scanFormat, path) });
|
|
7048
|
+
const data = { ...a.data, artifact: path };
|
|
7049
|
+
if (a.kind === "scan" && scanFormat) data.format = scanFormat;
|
|
7050
|
+
if (counts) Object.assign(data, counts);
|
|
7051
|
+
else if (wantCounts) warnings.push(`${file} is too large to compute SARIF counts from (over ${MAX_SARIF_BYTES} bytes); skipping`);
|
|
7020
7052
|
out.push({ kind: a.kind, name: a.name, status: a.status, data });
|
|
7021
7053
|
}
|
|
7022
7054
|
return { attestations: out, files, warnings };
|
|
7023
7055
|
}
|
|
7024
7056
|
|
|
7057
|
+
// src/core/artifacts.ts
|
|
7058
|
+
var import_path3 = require("path");
|
|
7059
|
+
var MAX_ARTIFACT_FILES = 50;
|
|
7060
|
+
var TYPES = {
|
|
7061
|
+
".zip": "application/zip",
|
|
7062
|
+
".gz": "application/gzip",
|
|
7063
|
+
".tgz": "application/gzip",
|
|
7064
|
+
".tar": "application/x-tar",
|
|
7065
|
+
".json": "application/json",
|
|
7066
|
+
".xml": "application/xml",
|
|
7067
|
+
".pdf": "application/pdf",
|
|
7068
|
+
".txt": "text/plain",
|
|
7069
|
+
".log": "text/plain",
|
|
7070
|
+
".info": "text/plain",
|
|
7071
|
+
".md": "text/markdown",
|
|
7072
|
+
".csv": "text/csv",
|
|
7073
|
+
".html": "text/html",
|
|
7074
|
+
".htm": "text/html",
|
|
7075
|
+
".png": "image/png",
|
|
7076
|
+
".jpg": "image/jpeg",
|
|
7077
|
+
".jpeg": "image/jpeg",
|
|
7078
|
+
".svg": "image/svg+xml"
|
|
7079
|
+
};
|
|
7080
|
+
var contentTypeFor = (path) => TYPES[(0, import_path3.extname)(path).toLowerCase()] ?? "application/octet-stream";
|
|
7081
|
+
async function collectArtifacts(patterns, workspace, deps) {
|
|
7082
|
+
const warnings = [];
|
|
7083
|
+
if (!patterns.length) return { files: [], warnings };
|
|
7084
|
+
const find = deps.glob;
|
|
7085
|
+
const inside = [];
|
|
7086
|
+
for (const absolute of [...new Set(await find(patterns))]) {
|
|
7087
|
+
const checked = await checkWorkspacePath(workspace, absolute, { display: displayName(workspace, absolute) });
|
|
7088
|
+
if (!checked.ok) {
|
|
7089
|
+
if (checked.reason !== "missing") warnings.push(`${checked.message} and was not uploaded`);
|
|
7090
|
+
continue;
|
|
7091
|
+
}
|
|
7092
|
+
inside.push({ absolute: checked.file.absolute, path: checked.file.path });
|
|
7093
|
+
}
|
|
7094
|
+
inside.sort((a, b) => a.path.localeCompare(b.path));
|
|
7095
|
+
if (inside.length > MAX_ARTIFACT_FILES) warnings.push(`${inside.length} files matched; only the first ${MAX_ARTIFACT_FILES} are uploaded`);
|
|
7096
|
+
const files = [];
|
|
7097
|
+
for (const f of inside.slice(0, MAX_ARTIFACT_FILES)) {
|
|
7098
|
+
const hashed = await hashWorkspaceFile(workspace, f.path);
|
|
7099
|
+
if (!hashed.ok) {
|
|
7100
|
+
if (hashed.reason !== "missing" && hashed.reason !== "not-file") warnings.push(`${hashed.message} and was not uploaded`);
|
|
7101
|
+
continue;
|
|
7102
|
+
}
|
|
7103
|
+
if (hashed.size === 0) continue;
|
|
7104
|
+
files.push({ path: f.path, absolute: f.absolute, workspace, size: hashed.size, sha256: hashed.sha256, contentType: contentTypeFor(f.path) });
|
|
7105
|
+
}
|
|
7106
|
+
return { files, warnings };
|
|
7107
|
+
}
|
|
7108
|
+
|
|
7025
7109
|
// src/core/request.ts
|
|
7026
7110
|
function buildIngestRequest(p) {
|
|
7027
7111
|
return {
|
|
@@ -7092,23 +7176,34 @@ var sendReport = (baseUrl, provider, req, audience, deps, azure) => postJson(`${
|
|
|
7092
7176
|
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
7177
|
|
|
7094
7178
|
// src/core/upload.ts
|
|
7095
|
-
var import_fs4 = require("fs");
|
|
7096
7179
|
var http = __toESM(require("http"));
|
|
7097
7180
|
var https = __toESM(require("https"));
|
|
7098
|
-
|
|
7181
|
+
var refused = (message) => Object.assign(new Error(message), { retryable: false });
|
|
7182
|
+
async function openForUpload(file) {
|
|
7183
|
+
const opened = await openWorkspaceFile(file.workspace, file.path);
|
|
7184
|
+
if (!opened.ok) throw refused(`${opened.message} and was not uploaded`);
|
|
7185
|
+
if (opened.size !== file.size) {
|
|
7186
|
+
await opened.handle.close();
|
|
7187
|
+
throw refused(`${file.path} changed since it was hashed and was not uploaded`);
|
|
7188
|
+
}
|
|
7189
|
+
return opened.handle;
|
|
7190
|
+
}
|
|
7191
|
+
async function putOnce(slot, file) {
|
|
7192
|
+
const handle = await openForUpload(file);
|
|
7193
|
+
const body = handle.createReadStream({ start: 0 });
|
|
7099
7194
|
return new Promise((resolve3, reject) => {
|
|
7100
7195
|
const url = new URL(slot.url);
|
|
7101
7196
|
const mod = url.protocol === "http:" ? http : https;
|
|
7102
7197
|
const headers = Object.fromEntries(Object.entries(slot.headers).filter(([k]) => !/^(proxy-)?authorization$|^cookie$/i.test(k)));
|
|
7103
7198
|
const req = mod.request(url, { method: "PUT", headers, timeout: 10 * 6e4 }, (res) => {
|
|
7104
|
-
let
|
|
7199
|
+
let body2 = "";
|
|
7105
7200
|
res.setEncoding("utf8");
|
|
7106
7201
|
res.on("data", (c) => {
|
|
7107
|
-
if (
|
|
7202
|
+
if (body2.length < 4e3) body2 += c;
|
|
7108
7203
|
});
|
|
7109
7204
|
res.on("end", () => {
|
|
7110
7205
|
if (res.statusCode && res.statusCode < 300) return resolve3();
|
|
7111
|
-
const code = /<Code>([^<]+)<\/Code>/.exec(
|
|
7206
|
+
const code = /<Code>([^<]+)<\/Code>/.exec(body2)?.[1] ?? `HTTP ${res.statusCode}`;
|
|
7112
7207
|
const err = new Error(`${file.path}: upload refused (${code})`);
|
|
7113
7208
|
err.retryable = (res.statusCode ?? 0) >= 500;
|
|
7114
7209
|
reject(err);
|
|
@@ -7123,8 +7218,8 @@ function putOnce(slot, file) {
|
|
|
7123
7218
|
if (e.retryable === void 0) e.retryable = true;
|
|
7124
7219
|
reject(e);
|
|
7125
7220
|
});
|
|
7126
|
-
|
|
7127
|
-
});
|
|
7221
|
+
body.on("error", reject).pipe(req);
|
|
7222
|
+
}).finally(() => body.destroy());
|
|
7128
7223
|
}
|
|
7129
7224
|
async function uploadFile(slot, file, attempts = 2) {
|
|
7130
7225
|
for (let i = 1; ; i++) {
|
|
@@ -7420,7 +7515,7 @@ function redactDeep(value, values) {
|
|
|
7420
7515
|
}
|
|
7421
7516
|
|
|
7422
7517
|
// src/cli/glob.ts
|
|
7423
|
-
var
|
|
7518
|
+
var import_path4 = require("path");
|
|
7424
7519
|
var { glob } = require_dist2();
|
|
7425
7520
|
function workspaceGlob(workspace, warn) {
|
|
7426
7521
|
return async (patterns) => {
|
|
@@ -7429,9 +7524,9 @@ function workspaceGlob(workspace, warn) {
|
|
|
7429
7524
|
for (const raw of patterns) {
|
|
7430
7525
|
const neg = raw.startsWith("!");
|
|
7431
7526
|
let p = neg ? raw.slice(1) : raw;
|
|
7432
|
-
if ((0,
|
|
7433
|
-
const rel = (0,
|
|
7434
|
-
if (!rel || rel.startsWith("..") || (0,
|
|
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)) {
|
|
7435
7530
|
warn(`${raw} is outside the workspace and was skipped`);
|
|
7436
7531
|
continue;
|
|
7437
7532
|
}
|
|
@@ -7441,7 +7536,7 @@ function workspaceGlob(workspace, warn) {
|
|
|
7441
7536
|
}
|
|
7442
7537
|
if (!include.length) return [];
|
|
7443
7538
|
const found = await glob(include, { cwd: workspace, ignore, absolute: true, onlyFiles: true, dot: true, followSymbolicLinks: true });
|
|
7444
|
-
return found.map((f) => (0,
|
|
7539
|
+
return found.map((f) => (0, import_path4.resolve)(f)).sort();
|
|
7445
7540
|
};
|
|
7446
7541
|
}
|
|
7447
7542
|
|
|
@@ -7504,16 +7599,17 @@ var SKIPPED = {
|
|
|
7504
7599
|
monthly_quota: "your organization's monthly upload allowance is used up"
|
|
7505
7600
|
};
|
|
7506
7601
|
var DEFAULT_SUMMARY_FILE = "prodgator-summary.md";
|
|
7507
|
-
async function readSummary(workspace, summary, file,
|
|
7602
|
+
async function readSummary(workspace, summary, file, log) {
|
|
7508
7603
|
if (summary) return summary;
|
|
7509
7604
|
const path = file ?? DEFAULT_SUMMARY_FILE;
|
|
7510
|
-
const
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7605
|
+
const read = await readWorkspaceText(workspace, path);
|
|
7606
|
+
if (read.ok) return read.text;
|
|
7607
|
+
if (read.reason === "missing") {
|
|
7608
|
+
if (file) log.info(`No summary file at ${path}; sending the report without a summary.`);
|
|
7609
|
+
} else {
|
|
7610
|
+
log.warn(`${read.message}; sending the report without a summary.`);
|
|
7516
7611
|
}
|
|
7612
|
+
return null;
|
|
7517
7613
|
}
|
|
7518
7614
|
function mergeFiles(first, rest, warn) {
|
|
7519
7615
|
const byPath = /* @__PURE__ */ new Map();
|
|
@@ -7527,7 +7623,7 @@ async function run(deps = {}) {
|
|
|
7527
7623
|
const argv = deps.argv ?? process.argv.slice(2);
|
|
7528
7624
|
const log = deps.log ?? { info: (m) => console.log(m), warn: (m) => console.log(`Warning: ${m}`), error: (m) => console.error(m) };
|
|
7529
7625
|
if (argv.includes("--version")) {
|
|
7530
|
-
log.info("3.19.
|
|
7626
|
+
log.info("3.19.1");
|
|
7531
7627
|
return 0;
|
|
7532
7628
|
}
|
|
7533
7629
|
let failOnError = false;
|
|
@@ -7545,7 +7641,7 @@ async function run(deps = {}) {
|
|
|
7545
7641
|
const problem = tokenProblem(token, opts.audience, ci.provider, variable, (deps.nowMs ?? Date.now)());
|
|
7546
7642
|
if (problem) throw new CliError(problem);
|
|
7547
7643
|
if (opts.gate) {
|
|
7548
|
-
const client2 = { getToken: async () => token, fetch: deps.fetch, sleep: deps.sleep, fixedToken: true, userAgent: `prodgator-report/${"3.19.
|
|
7644
|
+
const client2 = { getToken: async () => token, fetch: deps.fetch, sleep: deps.sleep, fixedToken: true, userAgent: `prodgator-report/${"3.19.1"} (${ci.provider}; gate)` };
|
|
7549
7645
|
return await runGate(ci, { prodgatorUrl: opts.prodgatorUrl, audience: opts.audience, timeoutMinutes: opts.gateTimeoutMinutes }, client2, {
|
|
7550
7646
|
sleep: deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))),
|
|
7551
7647
|
nowMs: deps.nowMs ?? Date.now,
|
|
@@ -7554,11 +7650,11 @@ async function run(deps = {}) {
|
|
|
7554
7650
|
}
|
|
7555
7651
|
const secrets = secretValues(env, [variable, ...opts.redactVariables]);
|
|
7556
7652
|
const glob2 = workspaceGlob(ci.workspace, (w) => log.warn(w));
|
|
7557
|
-
const rawSummary = await readSummary(ci.workspace, opts.summary, opts.summaryFile,
|
|
7653
|
+
const rawSummary = await readSummary(ci.workspace, opts.summary, opts.summaryFile, log);
|
|
7558
7654
|
const summary = rawSummary ? truncateUtf8(redactText(rawSummary, secrets), MAX_SUMMARY_BYTES).text : null;
|
|
7559
|
-
const scanned = await resolveAttestationFiles(parseAttestationsInput(opts.attestations, ci.workspace), ci.workspace);
|
|
7655
|
+
const scanned = await resolveAttestationFiles(await parseAttestationsInput(opts.attestations, ci.workspace), ci.workspace);
|
|
7560
7656
|
const attestations = redactDeep(scanned.attestations, secrets);
|
|
7561
|
-
const junit = await junitAttestation(opts.junit, { glob: glob2 });
|
|
7657
|
+
const junit = await junitAttestation(opts.junit, { glob: glob2, workspace: ci.workspace, warn: (w) => log.warn(w) });
|
|
7562
7658
|
if (junit) attestations.push(junit);
|
|
7563
7659
|
const globbed = await collectArtifacts(opts.artifacts, ci.workspace, { glob: glob2 });
|
|
7564
7660
|
[...scanned.warnings, ...globbed.warnings].forEach((w) => log.warn(w));
|
|
@@ -7568,7 +7664,7 @@ async function run(deps = {}) {
|
|
|
7568
7664
|
fetch: deps.fetch,
|
|
7569
7665
|
sleep: deps.sleep,
|
|
7570
7666
|
fixedToken: true,
|
|
7571
|
-
userAgent: `prodgator-report/${"3.19.
|
|
7667
|
+
userAgent: `prodgator-report/${"3.19.1"} (${ci.provider})`
|
|
7572
7668
|
};
|
|
7573
7669
|
const request = buildIngestRequest({ job: { name: opts.jobName ?? ci.jobName, matrix: opts.matrix, runner: ci.runner }, name: opts.name, summary, attestations, files });
|
|
7574
7670
|
const res = await sendReport(opts.prodgatorUrl, ci.provider, request, opts.audience, client, ci.azure);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prodgator/report",
|
|
3
|
-
"version": "3.19.
|
|
3
|
+
"version": "3.19.1",
|
|
4
4
|
"description": "Send CI job summaries, test results, attestations and artifacts to Prodgator, authenticated with the CI system's OIDC token",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://docs.prodgator.io/pipelines/run-reports",
|