@plune-ai/cli 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -8
- package/dist/cli.cjs +307 -33
- package/dist/index.cjs +9 -2
- package/dist/index.js +9 -2
- package/dist/reporter-core.cjs +278 -22
- package/dist/reporter-core.js +274 -18
- package/package.json +1 -1
package/dist/reporter-core.cjs
CHANGED
|
@@ -48,8 +48,8 @@ __export(reporter_core_exports, {
|
|
|
48
48
|
module.exports = __toCommonJS(reporter_core_exports);
|
|
49
49
|
|
|
50
50
|
// src/reporter-core/session.ts
|
|
51
|
-
var
|
|
52
|
-
var
|
|
51
|
+
var import_node_crypto2 = require("crypto");
|
|
52
|
+
var import_node_fs5 = require("fs");
|
|
53
53
|
var import_node_path3 = require("path");
|
|
54
54
|
var import_node_util2 = require("util");
|
|
55
55
|
|
|
@@ -519,6 +519,27 @@ function webLink(href) {
|
|
|
519
519
|
return href !== void 0 && /^https?:\/\//i.test(href) && URL.canParse(href) ? href : void 0;
|
|
520
520
|
}
|
|
521
521
|
|
|
522
|
+
// src/reporter-core/files-batch.ts
|
|
523
|
+
var import_node_crypto = require("crypto");
|
|
524
|
+
var import_node_fs4 = require("fs");
|
|
525
|
+
var FILES_BATCH_MAX = 100;
|
|
526
|
+
var DIRECT_BATCH_BYTES = 6 * 1024 * 1024;
|
|
527
|
+
var BATCH_PIPELINES = 2;
|
|
528
|
+
async function sha256File(path3) {
|
|
529
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
530
|
+
for await (const part of (0, import_node_fs4.createReadStream)(path3)) hash.update(part);
|
|
531
|
+
return hash.digest("hex");
|
|
532
|
+
}
|
|
533
|
+
var sha256Bytes = (bytes) => (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex");
|
|
534
|
+
var BUCKET_CODES = /* @__PURE__ */ new Set([
|
|
535
|
+
"bucket_unreachable",
|
|
536
|
+
"not_connected",
|
|
537
|
+
"platform_not_configured",
|
|
538
|
+
"upload_expired",
|
|
539
|
+
"file_mismatch",
|
|
540
|
+
"file_missing"
|
|
541
|
+
]);
|
|
542
|
+
|
|
522
543
|
// src/reporter-core/fallback.ts
|
|
523
544
|
var fs = __toESM(require("fs"), 1);
|
|
524
545
|
var path2 = __toESM(require("path"), 1);
|
|
@@ -631,7 +652,7 @@ function tally(counts, noun) {
|
|
|
631
652
|
}
|
|
632
653
|
function boundKey(key) {
|
|
633
654
|
if (key.value.length <= KEY_MAX) return key;
|
|
634
|
-
const digest = (0,
|
|
655
|
+
const digest = (0, import_node_crypto2.createHash)("sha256").update(key.value).digest("hex").slice(0, 16);
|
|
635
656
|
let head = key.value.slice(0, KEY_MAX - digest.length - 1);
|
|
636
657
|
if (/[\uD800-\uDBFF]$/.test(head)) head = head.slice(0, -1);
|
|
637
658
|
return { ...key, value: `${head}#${digest}` };
|
|
@@ -685,7 +706,7 @@ async function startRun(passed, declared) {
|
|
|
685
706
|
`));
|
|
686
707
|
const fallbackPath = cfg.fallbackPath ?? DEFAULT_FALLBACK_PATH;
|
|
687
708
|
const externalKey = cfg.externalKey ?? null;
|
|
688
|
-
const sessionMarker = (0,
|
|
709
|
+
const sessionMarker = (0, import_node_crypto2.randomUUID)();
|
|
689
710
|
const batchSize = Math.min(cfg.batchSize ?? BATCH_DEFAULT, BATCH_MAX);
|
|
690
711
|
const offerDiscovered = cfg.offerDiscovered ?? false;
|
|
691
712
|
const token = cfg.token ?? loadToken() ?? "";
|
|
@@ -693,6 +714,9 @@ async function startRun(passed, declared) {
|
|
|
693
714
|
const uploadRetries = Math.min(Math.max(Math.floor(cfg.uploadRetries ?? UPLOAD_RETRIES.default), UPLOAD_RETRIES.min), UPLOAD_RETRIES.max);
|
|
694
715
|
let retried = 0;
|
|
695
716
|
let uploadMs = 0;
|
|
717
|
+
let requests = 0;
|
|
718
|
+
let existed = 0;
|
|
719
|
+
let batchMissing = false;
|
|
696
720
|
const client = createClient({
|
|
697
721
|
apiUrl: resolveApiUrl(cfg.apiUrl),
|
|
698
722
|
token,
|
|
@@ -876,6 +900,7 @@ async function startRun(passed, declared) {
|
|
|
876
900
|
else log(`plune: ${KINDS[kind].label} "${name}" not uploaded \u2014 ${SKIPPED[why]}.`);
|
|
877
901
|
}
|
|
878
902
|
function ask(job, size) {
|
|
903
|
+
requests += 1;
|
|
879
904
|
return client.post(`/v1/results/${encodeURIComponent(job.id)}/files/uploads`, {
|
|
880
905
|
files: [{ name: job.name, contentType: job.type, size }]
|
|
881
906
|
}, uploadRetries);
|
|
@@ -910,13 +935,14 @@ async function startRun(passed, declared) {
|
|
|
910
935
|
if (job.body !== void 0) bytes = job.body;
|
|
911
936
|
else {
|
|
912
937
|
try {
|
|
913
|
-
bytes = await
|
|
938
|
+
bytes = await import_node_fs5.promises.readFile(job.path);
|
|
914
939
|
} catch {
|
|
915
940
|
return skip(kind, job.name, "missing");
|
|
916
941
|
}
|
|
917
942
|
}
|
|
918
943
|
const refusal = kind === "text" ? textRefusal(bytes) : void 0;
|
|
919
944
|
if (refusal !== void 0) return skip(kind, job.name, refusal);
|
|
945
|
+
requests += 1;
|
|
920
946
|
const out = await client.upload(
|
|
921
947
|
`/v1/results/${encodeURIComponent(job.id)}/files?name=${encodeURIComponent(job.name)}`,
|
|
922
948
|
bytes,
|
|
@@ -939,6 +965,7 @@ async function startRun(passed, declared) {
|
|
|
939
965
|
if (fresh === void 0) return failedToBucket(job, "bad_answer");
|
|
940
966
|
link = fresh;
|
|
941
967
|
}
|
|
968
|
+
requests += 1;
|
|
942
969
|
const done2 = await client.post(`/v1/files/${encodeURIComponent(link.id)}/complete`, {}, uploadRetries);
|
|
943
970
|
if (!done2.ok) return failedToBucket(job, codeOf(done2));
|
|
944
971
|
countsOf(job.kind).uploaded += 1;
|
|
@@ -963,7 +990,7 @@ async function startRun(passed, declared) {
|
|
|
963
990
|
else if (job.path === void 0) return void skip(kind, job.name, "missing");
|
|
964
991
|
else {
|
|
965
992
|
try {
|
|
966
|
-
size = (await
|
|
993
|
+
size = (await import_node_fs5.promises.stat(job.path)).size;
|
|
967
994
|
} catch {
|
|
968
995
|
return void skip(kind, job.name, "missing");
|
|
969
996
|
}
|
|
@@ -999,6 +1026,245 @@ async function startRun(passed, declared) {
|
|
|
999
1026
|
}
|
|
1000
1027
|
return () => uploadToBucket(job, size, link);
|
|
1001
1028
|
}
|
|
1029
|
+
async function uploadEach(jobs) {
|
|
1030
|
+
const byResult = /* @__PURE__ */ new Map();
|
|
1031
|
+
for (const job of jobs) byResult.set(job.id, [...byResult.get(job.id) ?? [], job]);
|
|
1032
|
+
await Promise.all(
|
|
1033
|
+
[...byResult.values()].map(async (group) => {
|
|
1034
|
+
const work = [];
|
|
1035
|
+
for (const job of group) {
|
|
1036
|
+
const act = await plan(job);
|
|
1037
|
+
if (act !== void 0) work.push(gated(act));
|
|
1038
|
+
}
|
|
1039
|
+
await Promise.all(work);
|
|
1040
|
+
})
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
async function measure(job) {
|
|
1044
|
+
let size;
|
|
1045
|
+
if (job.body !== void 0) size = job.body.length;
|
|
1046
|
+
else if (job.path === void 0) return void skip(job.kind, job.name, "missing");
|
|
1047
|
+
else {
|
|
1048
|
+
try {
|
|
1049
|
+
size = (await import_node_fs5.promises.stat(job.path)).size;
|
|
1050
|
+
} catch {
|
|
1051
|
+
return void skip(job.kind, job.name, "missing");
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (size === 0) return void skip(job.kind, job.name, "empty");
|
|
1055
|
+
return size;
|
|
1056
|
+
}
|
|
1057
|
+
const routeMissing = (out) => !out.ok && out.status === 404 && out.detail === "" && out.code === void 0;
|
|
1058
|
+
function takeRoom(job) {
|
|
1059
|
+
if (job.kind === "file") {
|
|
1060
|
+
skip(job.kind, job.name, "noStorage");
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
const roomKey = `${job.id}/${job.kind}`;
|
|
1064
|
+
const left = rooms.get(roomKey) ?? KINDS[job.kind].room;
|
|
1065
|
+
if (left === 0) {
|
|
1066
|
+
skip(job.kind, job.name, KINDS[job.kind].limit);
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
rooms.set(roomKey, left - 1);
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
1072
|
+
function refusedItem(job, size, item) {
|
|
1073
|
+
const code = item.code ?? "refused";
|
|
1074
|
+
if (code === "duplicate") existed += 1;
|
|
1075
|
+
else if (code === "no_storage") skip(job.kind, job.name, refusedHere(job, size) ?? "noStorage");
|
|
1076
|
+
else if (code === "too_many_files") skip(job.kind, job.name, KINDS[job.kind].limit);
|
|
1077
|
+
else if (BUCKET_CODES.has(code)) failedToBucket(job, code);
|
|
1078
|
+
else failed(job, code);
|
|
1079
|
+
}
|
|
1080
|
+
const itemPause = (round) => new Promise((r) => setTimeout(r, Math.min(250 * 2 ** round, 4e3)));
|
|
1081
|
+
async function batchOf(jobs) {
|
|
1082
|
+
if (batchMissing) return uploadEach(jobs);
|
|
1083
|
+
const sized = [];
|
|
1084
|
+
for (const job of jobs) {
|
|
1085
|
+
const size = await measure(job);
|
|
1086
|
+
if (size === void 0) continue;
|
|
1087
|
+
try {
|
|
1088
|
+
sized.push({ job, size, sha256: job.body !== void 0 ? sha256Bytes(job.body) : await sha256File(job.path) });
|
|
1089
|
+
} catch {
|
|
1090
|
+
skip(job.kind, job.name, "missing");
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (sized.length === 0) return;
|
|
1094
|
+
const announced = await gated(() => {
|
|
1095
|
+
requests += 1;
|
|
1096
|
+
return client.post(
|
|
1097
|
+
"/v1/files/uploads-batch",
|
|
1098
|
+
{ items: sized.map((s) => ({ resultId: s.job.id, name: s.job.name, contentType: s.job.type, size: s.size, sha256: s.sha256 })) },
|
|
1099
|
+
uploadRetries
|
|
1100
|
+
);
|
|
1101
|
+
});
|
|
1102
|
+
if (routeMissing(announced)) {
|
|
1103
|
+
batchMissing = true;
|
|
1104
|
+
return uploadEach(sized.map((s) => s.job));
|
|
1105
|
+
}
|
|
1106
|
+
const verdicts = announced.ok ? announced.body?.items : void 0;
|
|
1107
|
+
if (!announced.ok || !Array.isArray(verdicts) || verdicts.length !== sized.length) {
|
|
1108
|
+
for (const s of sized) {
|
|
1109
|
+
if (announced.ok) failed(s.job, "bad_answer");
|
|
1110
|
+
else if (announced.code !== void 0) failedToBucket(s.job, announced.code);
|
|
1111
|
+
else failed(s.job, announced.detail || announced.kind);
|
|
1112
|
+
}
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
const puts = [];
|
|
1116
|
+
const directs = [];
|
|
1117
|
+
sized.forEach((s, i) => {
|
|
1118
|
+
const v = verdicts[i] ?? {};
|
|
1119
|
+
if (v.status === "exists") existed += 1;
|
|
1120
|
+
else if (v.status === "direct") {
|
|
1121
|
+
if (takeRoom(s.job)) directs.push(s);
|
|
1122
|
+
} else if (v.status === "upload") {
|
|
1123
|
+
const link = linkOf(v);
|
|
1124
|
+
if (link === void 0) return failed(s.job, "bad_answer");
|
|
1125
|
+
if (!bucketSeen) {
|
|
1126
|
+
bucketSeen = true;
|
|
1127
|
+
log(BUCKET_FOUND);
|
|
1128
|
+
}
|
|
1129
|
+
puts.push({ s, link });
|
|
1130
|
+
} else if (v.status === "refused") refusedItem(s.job, s.size, v);
|
|
1131
|
+
else failed(s.job, "bad_answer");
|
|
1132
|
+
});
|
|
1133
|
+
const toComplete = [];
|
|
1134
|
+
await Promise.all(
|
|
1135
|
+
puts.map(
|
|
1136
|
+
({ s, link: first }) => gated(async () => {
|
|
1137
|
+
const source = s.job.body !== void 0 ? { body: s.job.body } : { path: s.job.path, size: s.size };
|
|
1138
|
+
let link = first;
|
|
1139
|
+
for (let renewed = false; ; ) {
|
|
1140
|
+
const put = await client.put(link.url, link.headers, source);
|
|
1141
|
+
if (put.ok) break;
|
|
1142
|
+
if (put.status !== 403 || renewed) return failedToBucket(s.job, put.code);
|
|
1143
|
+
renewed = true;
|
|
1144
|
+
requests += 1;
|
|
1145
|
+
const again = await client.post(
|
|
1146
|
+
"/v1/files/uploads-batch",
|
|
1147
|
+
{ items: [{ resultId: s.job.id, name: s.job.name, contentType: s.job.type, size: s.size, sha256: s.sha256 }] },
|
|
1148
|
+
uploadRetries
|
|
1149
|
+
);
|
|
1150
|
+
const fresh = again.ok ? linkOf(again.body?.items?.[0]) : void 0;
|
|
1151
|
+
if (fresh === void 0) return failedToBucket(s.job, again.ok ? "bad_answer" : codeOf(again));
|
|
1152
|
+
link = fresh;
|
|
1153
|
+
}
|
|
1154
|
+
toComplete.push({ job: s.job, id: link.id });
|
|
1155
|
+
})
|
|
1156
|
+
)
|
|
1157
|
+
);
|
|
1158
|
+
await complete(toComplete);
|
|
1159
|
+
await sendDirect(directs);
|
|
1160
|
+
}
|
|
1161
|
+
async function complete(items) {
|
|
1162
|
+
for (let round = 0; items.length > 0; round += 1) {
|
|
1163
|
+
const out = await gated(() => {
|
|
1164
|
+
requests += 1;
|
|
1165
|
+
return client.post("/v1/files/complete-batch", { ids: items.map((i) => i.id) }, uploadRetries);
|
|
1166
|
+
});
|
|
1167
|
+
if (routeMissing(out)) {
|
|
1168
|
+
await Promise.all(
|
|
1169
|
+
items.map(
|
|
1170
|
+
(i) => gated(async () => {
|
|
1171
|
+
requests += 1;
|
|
1172
|
+
const one = await client.post(`/v1/files/${encodeURIComponent(i.id)}/complete`, {}, uploadRetries);
|
|
1173
|
+
if (one.ok) countsOf(i.job.kind).uploaded += 1;
|
|
1174
|
+
else failedToBucket(i.job, codeOf(one));
|
|
1175
|
+
})
|
|
1176
|
+
)
|
|
1177
|
+
);
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const verdicts = out.ok ? out.body?.items : void 0;
|
|
1181
|
+
if (!out.ok || !Array.isArray(verdicts) || verdicts.length !== items.length) {
|
|
1182
|
+
for (const i of items) failedToBucket(i.job, out.ok ? "bad_answer" : codeOf(out));
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
const again = [];
|
|
1186
|
+
items.forEach((i, at) => {
|
|
1187
|
+
const v = verdicts[at] ?? {};
|
|
1188
|
+
if (v.status === "ready") countsOf(i.job.kind).uploaded += 1;
|
|
1189
|
+
else if (v.retryable === true && round < Math.min(uploadRetries, 3)) again.push(i);
|
|
1190
|
+
else refusedItem(i.job, 0, v.status === "refused" ? v : { code: "bad_answer" });
|
|
1191
|
+
});
|
|
1192
|
+
items = again;
|
|
1193
|
+
if (items.length > 0) {
|
|
1194
|
+
retried += items.length;
|
|
1195
|
+
await itemPause(round);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
async function sendDirect(items) {
|
|
1200
|
+
const groups = [];
|
|
1201
|
+
let bytes = 0;
|
|
1202
|
+
for (const item of items) {
|
|
1203
|
+
const group = groups[groups.length - 1];
|
|
1204
|
+
if (group === void 0 || group.length >= FILES_BATCH_MAX || bytes + item.size > DIRECT_BATCH_BYTES) {
|
|
1205
|
+
groups.push([item]);
|
|
1206
|
+
bytes = item.size;
|
|
1207
|
+
} else {
|
|
1208
|
+
group.push(item);
|
|
1209
|
+
bytes += item.size;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
for (const group of groups) {
|
|
1213
|
+
const body = [];
|
|
1214
|
+
const sent = [];
|
|
1215
|
+
for (const { job, size } of group) {
|
|
1216
|
+
const { kind } = job;
|
|
1217
|
+
if (size > KINDS[kind].bytes) {
|
|
1218
|
+
skip(kind, job.name, KINDS[kind].size);
|
|
1219
|
+
continue;
|
|
1220
|
+
}
|
|
1221
|
+
let data;
|
|
1222
|
+
try {
|
|
1223
|
+
data = job.body ?? await import_node_fs5.promises.readFile(job.path);
|
|
1224
|
+
} catch {
|
|
1225
|
+
skip(kind, job.name, "missing");
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
const refusal = kind === "text" ? textRefusal(data) : void 0;
|
|
1229
|
+
if (refusal !== void 0) {
|
|
1230
|
+
skip(kind, job.name, refusal);
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
body.push({ resultId: job.id, name: job.name, contentType: job.type, data: data.toString("base64") });
|
|
1234
|
+
sent.push({ job, size });
|
|
1235
|
+
}
|
|
1236
|
+
if (body.length === 0) continue;
|
|
1237
|
+
const out = await gated(() => {
|
|
1238
|
+
requests += 1;
|
|
1239
|
+
return client.post("/v1/files/direct-batch", { items: body }, uploadRetries);
|
|
1240
|
+
});
|
|
1241
|
+
if (routeMissing(out)) {
|
|
1242
|
+
batchMissing = true;
|
|
1243
|
+
for (const { job, size } of sent) await gated(() => uploadDirect(job, size));
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
const verdicts = out.ok ? out.body?.items : void 0;
|
|
1247
|
+
if (!out.ok || !Array.isArray(verdicts) || verdicts.length !== sent.length) {
|
|
1248
|
+
for (const { job } of sent) failed(job, out.ok ? "bad_answer" : out.detail || out.kind);
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
sent.forEach(({ job }, i) => {
|
|
1252
|
+
const v = verdicts[i] ?? {};
|
|
1253
|
+
if (v.status === "stored") countsOf(job.kind).uploaded += 1;
|
|
1254
|
+
else if (v.status === "exists") existed += 1;
|
|
1255
|
+
else refusedItem(job, 0, v.status === "refused" ? v : { code: "bad_answer" });
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
async function uploadBatched(jobs) {
|
|
1260
|
+
const batches = chunk(jobs, FILES_BATCH_MAX);
|
|
1261
|
+
let next = 0;
|
|
1262
|
+
await Promise.all(
|
|
1263
|
+
Array.from({ length: Math.min(BATCH_PIPELINES, batches.length) }, async () => {
|
|
1264
|
+
for (let at = next++; at < batches.length; at = next++) await batchOf(batches[at]);
|
|
1265
|
+
})
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1002
1268
|
async function upload(items, filesByKey) {
|
|
1003
1269
|
if (filesByKey.size === 0 || !Array.isArray(items)) return;
|
|
1004
1270
|
const jobs = [];
|
|
@@ -1024,18 +1290,8 @@ async function startRun(passed, declared) {
|
|
|
1024
1290
|
}
|
|
1025
1291
|
}
|
|
1026
1292
|
const started = Date.now();
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
await Promise.all(
|
|
1030
|
-
[...byResult.values()].map(async (group) => {
|
|
1031
|
-
const work = [];
|
|
1032
|
-
for (const job of group) {
|
|
1033
|
-
const act = await plan(job);
|
|
1034
|
-
if (act !== void 0) work.push(gated(act));
|
|
1035
|
-
}
|
|
1036
|
-
await Promise.all(work);
|
|
1037
|
-
})
|
|
1038
|
-
);
|
|
1293
|
+
if (batchMissing) await uploadEach(jobs);
|
|
1294
|
+
else await uploadBatched(jobs);
|
|
1039
1295
|
uploadMs += Date.now() - started;
|
|
1040
1296
|
}
|
|
1041
1297
|
async function flush() {
|
|
@@ -1131,9 +1387,9 @@ async function startRun(passed, declared) {
|
|
|
1131
1387
|
if (shortened.size > 0) parts.push(`${shortened.size} test name(s) shortened to fit a key`);
|
|
1132
1388
|
const files = [stats.screenshots, stats.textFiles, stats.otherFiles];
|
|
1133
1389
|
const total = (what) => files.reduce((n, counts) => n + counts[what], 0);
|
|
1134
|
-
if (total("uploaded") + total("skipped") + total("failed") > 0) {
|
|
1390
|
+
if (total("uploaded") + total("skipped") + total("failed") + existed > 0) {
|
|
1135
1391
|
log(
|
|
1136
|
-
`plune: files \u2014 ${total("uploaded")} uploaded, ${total("skipped")} skipped, ${retried} retried, ${total("failed")} dropped, in ${(uploadMs / 1e3).toFixed(1)} s (${uploadsAtOnce} at once)`
|
|
1392
|
+
`plune: files \u2014 ${total("uploaded")} uploaded, ${total("skipped")} skipped, ${existed} already there (skipped), ${retried} retried, ${total("failed")} dropped, in ${(uploadMs / 1e3).toFixed(1)} s (${uploadsAtOnce} at once, ${requests} request(s) to Plune)`
|
|
1137
1393
|
);
|
|
1138
1394
|
}
|
|
1139
1395
|
log(`plune: ${parts.join(" \xB7 ")}`);
|
|
@@ -1192,9 +1448,9 @@ async function startRun(passed, declared) {
|
|
|
1192
1448
|
}
|
|
1193
1449
|
|
|
1194
1450
|
// src/util/hash.ts
|
|
1195
|
-
var
|
|
1451
|
+
var import_node_crypto3 = require("crypto");
|
|
1196
1452
|
function sha256(input) {
|
|
1197
|
-
return (0,
|
|
1453
|
+
return (0, import_node_crypto3.createHash)("sha256").update(input, "utf8").digest("hex");
|
|
1198
1454
|
}
|
|
1199
1455
|
|
|
1200
1456
|
// src/reporter-core/result-key.ts
|