wayfarer-cli 2.0.2 → 2.1.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 +12 -4
- package/dist/wayfarer-cli.mjs +1515 -335
- package/package.json +13 -2
package/dist/wayfarer-cli.mjs
CHANGED
|
@@ -7469,7 +7469,7 @@ function stringifyJson(value, space) {
|
|
|
7469
7469
|
}
|
|
7470
7470
|
}
|
|
7471
7471
|
var MAX_IMPORT_BYTES = 10 * 1024 * 1024;
|
|
7472
|
-
var IMPORT_TOO_LARGE = "
|
|
7472
|
+
var IMPORT_TOO_LARGE = "A file to import can be 10 MB at most, and this one is larger.";
|
|
7473
7473
|
function isOversizedImport(input) {
|
|
7474
7474
|
return typeof input === "string" && input.length > MAX_IMPORT_BYTES;
|
|
7475
7475
|
}
|
|
@@ -7554,7 +7554,10 @@ function newId() {
|
|
|
7554
7554
|
// ../core/src/view/json-path.ts
|
|
7555
7555
|
var EVERY = "\0";
|
|
7556
7556
|
function evaluatePath(value, path) {
|
|
7557
|
-
return walk(
|
|
7557
|
+
return walk(
|
|
7558
|
+
value,
|
|
7559
|
+
path.replace(/\[\*\]/g, `.${EVERY}`).replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean)
|
|
7560
|
+
);
|
|
7558
7561
|
}
|
|
7559
7562
|
function walk(value, segments) {
|
|
7560
7563
|
let current = value;
|
|
@@ -7610,7 +7613,14 @@ function resolveAuth(auth3, resolve) {
|
|
|
7610
7613
|
case "apikey":
|
|
7611
7614
|
return { ...auth3, key: resolve(auth3.key), value: resolve(auth3.value) };
|
|
7612
7615
|
case "awsv4":
|
|
7613
|
-
return {
|
|
7616
|
+
return {
|
|
7617
|
+
...auth3,
|
|
7618
|
+
accessKey: resolve(auth3.accessKey),
|
|
7619
|
+
secretKey: resolve(auth3.secretKey),
|
|
7620
|
+
sessionToken: resolve(auth3.sessionToken),
|
|
7621
|
+
region: resolve(auth3.region),
|
|
7622
|
+
service: resolve(auth3.service)
|
|
7623
|
+
};
|
|
7614
7624
|
case "oauth2":
|
|
7615
7625
|
return {
|
|
7616
7626
|
...auth3,
|
|
@@ -7655,7 +7665,13 @@ function credentialsOf(auth3) {
|
|
|
7655
7665
|
// ../core/src/auth/sigv4.ts
|
|
7656
7666
|
var utf8 = (text3) => new TextEncoder().encode(text3);
|
|
7657
7667
|
var sha256 = async (data) => new Uint8Array(await crypto.subtle.digest("SHA-256", data));
|
|
7658
|
-
var hmac = async (key, data) => new Uint8Array(
|
|
7668
|
+
var hmac = async (key, data) => new Uint8Array(
|
|
7669
|
+
await crypto.subtle.sign(
|
|
7670
|
+
"HMAC",
|
|
7671
|
+
await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]),
|
|
7672
|
+
utf8(data)
|
|
7673
|
+
)
|
|
7674
|
+
);
|
|
7659
7675
|
var hex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
7660
7676
|
var encode = (text3) => encodeURIComponent(text3).replace(/[!'()*]/g, (mark) => `%${mark.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
7661
7677
|
var decode = (text3) => {
|
|
@@ -7674,29 +7690,54 @@ async function signV4(request, credentials, at) {
|
|
|
7674
7690
|
const path = s3 ? segments.map((segment) => encode(decode(segment))).join("/") : `/${kept.map(encode).join("/")}${kept.length && url.pathname.endsWith("/") ? "/" : ""}`;
|
|
7675
7691
|
const query = url.search.slice(1).split("&").filter(Boolean).map((pair) => {
|
|
7676
7692
|
const cut = pair.indexOf("=");
|
|
7677
|
-
return [
|
|
7678
|
-
|
|
7693
|
+
return [
|
|
7694
|
+
encode(decode(cut === -1 ? pair : pair.slice(0, cut))),
|
|
7695
|
+
encode(decode(cut === -1 ? "" : pair.slice(cut + 1)))
|
|
7696
|
+
];
|
|
7697
|
+
}).sort(
|
|
7698
|
+
([keyA, valueA], [keyB, valueB]) => keyA === keyB ? valueA < valueB ? -1 : valueA > valueB ? 1 : 0 : keyA < keyB ? -1 : 1
|
|
7699
|
+
).map(([key2, value]) => `${key2}=${value}`).join("&");
|
|
7679
7700
|
const date = at.toISOString().replace(/[-:]|\.\d{3}/g, "");
|
|
7680
7701
|
const day = date.slice(0, 8);
|
|
7681
7702
|
const body3 = typeof request.body === "string" ? utf8(request.body) : new Uint8Array(request.body ?? []);
|
|
7682
7703
|
const bodyHash = hex(await sha256(body3));
|
|
7683
|
-
const added2 = [
|
|
7704
|
+
const added2 = [
|
|
7705
|
+
["X-Amz-Date", date],
|
|
7706
|
+
...credentials.sessionToken ? [["X-Amz-Security-Token", credentials.sessionToken]] : [],
|
|
7707
|
+
...s3 ? [["X-Amz-Content-Sha256", bodyHash]] : []
|
|
7708
|
+
];
|
|
7684
7709
|
const byName = /* @__PURE__ */ new Map();
|
|
7685
|
-
const own3 = request.headers.filter(
|
|
7710
|
+
const own3 = request.headers.filter(
|
|
7711
|
+
([name]) => !added2.some(([written]) => written.toLowerCase() === name.toLowerCase()) && name.toLowerCase() !== "host"
|
|
7712
|
+
);
|
|
7686
7713
|
for (const [name, value] of [["Host", url.host], ...own3, ...added2]) {
|
|
7687
7714
|
const key2 = name.toLowerCase();
|
|
7688
7715
|
byName.set(key2, [...byName.get(key2) ?? [], value.trim().replace(/\s+/g, " ")]);
|
|
7689
7716
|
}
|
|
7690
7717
|
const names = [...byName.keys()].sort();
|
|
7691
7718
|
const signedHeaders = names.join(";");
|
|
7692
|
-
const canonicalRequest = [
|
|
7719
|
+
const canonicalRequest = [
|
|
7720
|
+
request.method,
|
|
7721
|
+
path,
|
|
7722
|
+
query,
|
|
7723
|
+
...names.map((name) => `${name}:${(byName.get(name) ?? []).join(",")}`),
|
|
7724
|
+
"",
|
|
7725
|
+
signedHeaders,
|
|
7726
|
+
bodyHash
|
|
7727
|
+
].join("\n");
|
|
7693
7728
|
const scope = `${day}/${credentials.region}/${credentials.service}/aws4_request`;
|
|
7694
7729
|
const stringToSign = ["AWS4-HMAC-SHA256", date, scope, hex(await sha256(utf8(canonicalRequest)))].join("\n");
|
|
7695
7730
|
let key = utf8(`AWS4${credentials.secretKey}`);
|
|
7696
7731
|
for (const part of [day, credentials.region, credentials.service, "aws4_request"]) key = await hmac(key, part);
|
|
7697
7732
|
const signature = hex(await hmac(key, stringToSign));
|
|
7698
7733
|
return {
|
|
7699
|
-
headers: [
|
|
7734
|
+
headers: [
|
|
7735
|
+
...added2,
|
|
7736
|
+
[
|
|
7737
|
+
"Authorization",
|
|
7738
|
+
`AWS4-HMAC-SHA256 Credential=${credentials.accessKey}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`
|
|
7739
|
+
]
|
|
7740
|
+
],
|
|
7700
7741
|
canonicalRequest,
|
|
7701
7742
|
stringToSign,
|
|
7702
7743
|
signature
|
|
@@ -7708,7 +7749,13 @@ var OAuthError = class extends Error {
|
|
|
7708
7749
|
name = "OAuthError";
|
|
7709
7750
|
};
|
|
7710
7751
|
function oauthTokenKey(auth3) {
|
|
7711
|
-
return JSON.stringify([
|
|
7752
|
+
return JSON.stringify([
|
|
7753
|
+
auth3.grant,
|
|
7754
|
+
auth3.tokenUrl,
|
|
7755
|
+
auth3.clientId,
|
|
7756
|
+
auth3.scope,
|
|
7757
|
+
auth3.grant === "password" ? auth3.username : ""
|
|
7758
|
+
]);
|
|
7712
7759
|
}
|
|
7713
7760
|
|
|
7714
7761
|
// ../core/src/model/collection.ts
|
|
@@ -7733,7 +7780,12 @@ function childrenInOrder(tree, parent) {
|
|
|
7733
7780
|
return entries.sort((a, b) => a.order - b.order || a.kind - b.kind || a.id.localeCompare(b.id)).map((entry) => entry.child);
|
|
7734
7781
|
}
|
|
7735
7782
|
function ancestorsOf(collection, folders, folderId) {
|
|
7736
|
-
const named2 = (kind, { name, variables: variables2, auth: auth3, scripts: scripts2 }) => ({
|
|
7783
|
+
const named2 = (kind, { name, variables: variables2, auth: auth3, scripts: scripts2 }) => ({
|
|
7784
|
+
name: `${kind} "${name}"`,
|
|
7785
|
+
variables: variables2,
|
|
7786
|
+
auth: auth3,
|
|
7787
|
+
scripts: scripts2
|
|
7788
|
+
});
|
|
7737
7789
|
return [named2("collection", collection), ...folderChain(folders, folderId).map((folder) => named2("folder", folder))];
|
|
7738
7790
|
}
|
|
7739
7791
|
var ENVIRONMENTS_FORMAT = "wayfarer/environments";
|
|
@@ -7752,7 +7804,15 @@ var RAW_CONTENT_TYPES = {
|
|
|
7752
7804
|
javascript: "application/javascript"
|
|
7753
7805
|
};
|
|
7754
7806
|
var OAUTH_GRANTS = ["authorization_code", "client_credentials", "password"];
|
|
7755
|
-
var AUTH_TYPES = [
|
|
7807
|
+
var AUTH_TYPES = [
|
|
7808
|
+
"inherit",
|
|
7809
|
+
"none",
|
|
7810
|
+
"bearer",
|
|
7811
|
+
"basic",
|
|
7812
|
+
"apikey",
|
|
7813
|
+
"awsv4",
|
|
7814
|
+
"oauth2"
|
|
7815
|
+
];
|
|
7756
7816
|
var ASSERTION_TARGETS = ["status", "body", "header", "duration"];
|
|
7757
7817
|
var ASSERTION_OPERATORS = [
|
|
7758
7818
|
"equals",
|
|
@@ -7828,7 +7888,8 @@ var Checker = class {
|
|
|
7828
7888
|
this.string(row, "key", rowPath);
|
|
7829
7889
|
this.string(row, "value", rowPath);
|
|
7830
7890
|
this.enabled(row, rowPath);
|
|
7831
|
-
if (Object.hasOwn(row, "json") && (field(row, "json") !== true || !isJsonText(field(row, "value"))))
|
|
7891
|
+
if (Object.hasOwn(row, "json") && (field(row, "json") !== true || !isJsonText(field(row, "value"))))
|
|
7892
|
+
this.fail(`${rowPath}.json`, "Value must be true, on a value that is JSON.");
|
|
7832
7893
|
});
|
|
7833
7894
|
}
|
|
7834
7895
|
/** `inherit` is for a request and a folder: a collection has nothing above it. */
|
|
@@ -7850,9 +7911,11 @@ var Checker = class {
|
|
|
7850
7911
|
for (const key of ["accessKey", "secretKey", "sessionToken", "region", "service"]) this.string(auth3, key, path);
|
|
7851
7912
|
} else if (type === "oauth2") {
|
|
7852
7913
|
this.oneOf(auth3, "grant", path, OAUTH_GRANTS);
|
|
7853
|
-
for (const key of ["authUrl", "tokenUrl", "clientId", "clientSecret", "scope", "username", "password"])
|
|
7914
|
+
for (const key of ["authUrl", "tokenUrl", "clientId", "clientSecret", "scope", "username", "password"])
|
|
7915
|
+
this.string(auth3, key, path);
|
|
7854
7916
|
this.oneOf(auth3, "clientAuth", path, ["header", "body"]);
|
|
7855
|
-
if (typeof field(auth3, "retryOn401") !== "boolean")
|
|
7917
|
+
if (typeof field(auth3, "retryOn401") !== "boolean")
|
|
7918
|
+
this.fail(`${path}.retryOn401`, "Value must be true or false.");
|
|
7856
7919
|
}
|
|
7857
7920
|
}
|
|
7858
7921
|
scripts(value, path) {
|
|
@@ -7989,16 +8052,12 @@ function validateCollection(input) {
|
|
|
7989
8052
|
if (!Array.isArray(value.folders)) {
|
|
7990
8053
|
errors.push({ path: "folders", message: "Folders must be an array." });
|
|
7991
8054
|
} else {
|
|
7992
|
-
value.folders.forEach(
|
|
7993
|
-
(folder, index) => validateFolderDoc(folder, `folders[${index}]`, errors)
|
|
7994
|
-
);
|
|
8055
|
+
value.folders.forEach((folder, index) => validateFolderDoc(folder, `folders[${index}]`, errors));
|
|
7995
8056
|
}
|
|
7996
8057
|
if (!Array.isArray(value.requests)) {
|
|
7997
8058
|
errors.push({ path: "requests", message: "Requests must be an array." });
|
|
7998
8059
|
} else {
|
|
7999
|
-
value.requests.forEach(
|
|
8000
|
-
(request, index) => validateRequestDoc(request, `requests[${index}]`, errors)
|
|
8001
|
-
);
|
|
8060
|
+
value.requests.forEach((request, index) => validateRequestDoc(request, `requests[${index}]`, errors));
|
|
8002
8061
|
}
|
|
8003
8062
|
if (!value.meta) {
|
|
8004
8063
|
errors.push({ path: "meta", message: "Missing export meta block." });
|
|
@@ -8047,7 +8106,7 @@ function buildPlan(payload, duplicate) {
|
|
|
8047
8106
|
{
|
|
8048
8107
|
type: "collection",
|
|
8049
8108
|
name: payload.collection.name,
|
|
8050
|
-
id: payload.collection.
|
|
8109
|
+
id: payload.collection.meta.id,
|
|
8051
8110
|
action
|
|
8052
8111
|
}
|
|
8053
8112
|
];
|
|
@@ -8055,7 +8114,7 @@ function buildPlan(payload, duplicate) {
|
|
|
8055
8114
|
entries.push({
|
|
8056
8115
|
type: "folder",
|
|
8057
8116
|
name: folder.name,
|
|
8058
|
-
id: folder.
|
|
8117
|
+
id: folder.meta.id,
|
|
8059
8118
|
action
|
|
8060
8119
|
});
|
|
8061
8120
|
}
|
|
@@ -8063,7 +8122,7 @@ function buildPlan(payload, duplicate) {
|
|
|
8063
8122
|
entries.push({
|
|
8064
8123
|
type: "request",
|
|
8065
8124
|
name: request.name || request.url,
|
|
8066
|
-
id: request.
|
|
8125
|
+
id: request.meta.id,
|
|
8067
8126
|
action
|
|
8068
8127
|
});
|
|
8069
8128
|
}
|
|
@@ -8176,9 +8235,7 @@ function touchMeta(meta) {
|
|
|
8176
8235
|
return { ...meta, updatedAt: Date.now() };
|
|
8177
8236
|
}
|
|
8178
8237
|
function ensureDocId(doc) {
|
|
8179
|
-
|
|
8180
|
-
doc.id = doc.meta.id;
|
|
8181
|
-
}
|
|
8238
|
+
doc.id = doc.meta.id;
|
|
8182
8239
|
return doc;
|
|
8183
8240
|
}
|
|
8184
8241
|
|
|
@@ -8242,7 +8299,8 @@ function graphqlPayload(query, variables2) {
|
|
|
8242
8299
|
if (!variables2.trim()) return { text: JSON.stringify({ query }) };
|
|
8243
8300
|
const parsed2 = parseJson(variables2);
|
|
8244
8301
|
if (!parsed2.ok) return { error: "The GraphQL variables are not JSON." };
|
|
8245
|
-
if (typeof parsed2.value !== "object" || parsed2.value === null || Array.isArray(parsed2.value))
|
|
8302
|
+
if (typeof parsed2.value !== "object" || parsed2.value === null || Array.isArray(parsed2.value))
|
|
8303
|
+
return { error: "The GraphQL variables must be a JSON object." };
|
|
8246
8304
|
return { text: JSON.stringify({ query, variables: parsed2.value }) };
|
|
8247
8305
|
}
|
|
8248
8306
|
function graphqlText(query, variables2) {
|
|
@@ -8317,7 +8375,10 @@ var Redactor = class {
|
|
|
8317
8375
|
}
|
|
8318
8376
|
/** Headers as sent or received: a credential header's value is masked whole. */
|
|
8319
8377
|
headers(headers2, options = {}) {
|
|
8320
|
-
return headers2.map(([name, value]) => [
|
|
8378
|
+
return headers2.map(([name, value]) => [
|
|
8379
|
+
this.text(name),
|
|
8380
|
+
!options.credentials && isCredentialHeader(name) ? MASK : this.text(value)
|
|
8381
|
+
]);
|
|
8321
8382
|
}
|
|
8322
8383
|
/**
|
|
8323
8384
|
* Rows of a template (headers, or variables by name): a value typed in for
|
|
@@ -8325,7 +8386,10 @@ var Redactor = class {
|
|
|
8325
8386
|
* credential but a reference to one, and is kept.
|
|
8326
8387
|
*/
|
|
8327
8388
|
rows(rows2, options = {}) {
|
|
8328
|
-
return rows2.map((row) => ({
|
|
8389
|
+
return rows2.map((row) => ({
|
|
8390
|
+
...row,
|
|
8391
|
+
value: isCredentialHeader(row.key) ? this.credential(row.value, options) : this.text(row.value)
|
|
8392
|
+
}));
|
|
8329
8393
|
}
|
|
8330
8394
|
auth(auth3, options = {}) {
|
|
8331
8395
|
switch (auth3.type) {
|
|
@@ -8336,9 +8400,17 @@ var Redactor = class {
|
|
|
8336
8400
|
case "apikey":
|
|
8337
8401
|
return { ...auth3, value: this.credential(auth3.value, options) };
|
|
8338
8402
|
case "awsv4":
|
|
8339
|
-
return {
|
|
8403
|
+
return {
|
|
8404
|
+
...auth3,
|
|
8405
|
+
secretKey: this.credential(auth3.secretKey, options),
|
|
8406
|
+
sessionToken: this.credential(auth3.sessionToken, options)
|
|
8407
|
+
};
|
|
8340
8408
|
case "oauth2":
|
|
8341
|
-
return {
|
|
8409
|
+
return {
|
|
8410
|
+
...auth3,
|
|
8411
|
+
clientSecret: this.credential(auth3.clientSecret, options),
|
|
8412
|
+
password: this.credential(auth3.password, options)
|
|
8413
|
+
};
|
|
8342
8414
|
case "none":
|
|
8343
8415
|
case "inherit":
|
|
8344
8416
|
return auth3;
|
|
@@ -8359,9 +8431,15 @@ var Redactor = class {
|
|
|
8359
8431
|
return {
|
|
8360
8432
|
...body3,
|
|
8361
8433
|
...body3.raw && { raw: { ...body3.raw, text: this.text(body3.raw.text) } },
|
|
8362
|
-
...body3.graphql && {
|
|
8434
|
+
...body3.graphql && {
|
|
8435
|
+
graphql: { query: this.text(body3.graphql.query), variables: this.text(body3.graphql.variables) }
|
|
8436
|
+
},
|
|
8363
8437
|
...body3.urlencoded && { urlencoded: body3.urlencoded.map((row) => ({ ...row, value: this.text(row.value) })) },
|
|
8364
|
-
...body3.multipart && {
|
|
8438
|
+
...body3.multipart && {
|
|
8439
|
+
multipart: body3.multipart.map(
|
|
8440
|
+
(part) => part.kind === "text" ? { ...part, value: this.text(part.value) } : part
|
|
8441
|
+
)
|
|
8442
|
+
}
|
|
8365
8443
|
};
|
|
8366
8444
|
}
|
|
8367
8445
|
credential(value, options) {
|
|
@@ -8375,6 +8453,53 @@ function isCurlCommand(text3) {
|
|
|
8375
8453
|
return /^\s*curl(\.exe)?(\s|$)/i.test(text3);
|
|
8376
8454
|
}
|
|
8377
8455
|
|
|
8456
|
+
// ../core/src/import/script-scan.ts
|
|
8457
|
+
var START = String.raw`(?<![\w$.])`;
|
|
8458
|
+
var escape = (text3) => text3.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
8459
|
+
var member = (name) => ({
|
|
8460
|
+
api: name,
|
|
8461
|
+
finds: [[name, new RegExp(`${START}${escape(name)}(?![\\w$])`)]]
|
|
8462
|
+
});
|
|
8463
|
+
var required = (module) => [
|
|
8464
|
+
`require('${module}')`,
|
|
8465
|
+
new RegExp(`${START}require\\(\\s*["']${escape(module)}["']\\s*\\)`)
|
|
8466
|
+
];
|
|
8467
|
+
var called = (name) => [name, new RegExp(`${START}${escape(name)}\\s*\\(`)];
|
|
8468
|
+
var used = (name) => [name, new RegExp(`${START}${escape(name)}\\s*[.(]`)];
|
|
8469
|
+
var SCRIPT_SCAN = [
|
|
8470
|
+
...[
|
|
8471
|
+
"pm.cookies.get",
|
|
8472
|
+
"pm.cookies.has",
|
|
8473
|
+
"pm.cookies.toObject",
|
|
8474
|
+
"pm.cookies.jar",
|
|
8475
|
+
"pm.visualizer.set",
|
|
8476
|
+
"pm.vault.get",
|
|
8477
|
+
"pm.require",
|
|
8478
|
+
"postman.getResponseCookie"
|
|
8479
|
+
].map(member),
|
|
8480
|
+
{
|
|
8481
|
+
api: "legacy library globals (_, CryptoJS, tv4, cheerio, xml2Json, Backbone)",
|
|
8482
|
+
finds: ["tv4", "cheerio", "xml2Json", "Backbone"].map(used)
|
|
8483
|
+
},
|
|
8484
|
+
{
|
|
8485
|
+
api: "clearTimeout, setInterval, clearInterval",
|
|
8486
|
+
finds: ["clearTimeout", "setInterval", "clearInterval"].map(called)
|
|
8487
|
+
},
|
|
8488
|
+
...["ajv", "cheerio", "csv-parse/lib/sync", "postman-collection", "tv4", "xml2js"].map((module) => ({
|
|
8489
|
+
api: `require('${module}')`,
|
|
8490
|
+
finds: [required(module)]
|
|
8491
|
+
})),
|
|
8492
|
+
{
|
|
8493
|
+
api: "require of a Node module (buffer, events, path, querystring, stream, string_decoder, timers, url, util)",
|
|
8494
|
+
finds: ["buffer", "events", "path", "querystring", "stream", "string_decoder", "timers", "url", "util"].map(
|
|
8495
|
+
required
|
|
8496
|
+
)
|
|
8497
|
+
}
|
|
8498
|
+
];
|
|
8499
|
+
function unsupportedApis(script) {
|
|
8500
|
+
return SCRIPT_SCAN.flatMap((entry) => entry.finds).filter(([, pattern]) => pattern.test(script)).map(([label]) => label);
|
|
8501
|
+
}
|
|
8502
|
+
|
|
8378
8503
|
// ../core/src/model/variables.ts
|
|
8379
8504
|
var SECRET_REFERENCE = /^\s*\{\{\s*\$secret\.[a-z0-9-]+\s*\}\}\s*$/i;
|
|
8380
8505
|
function protectedSecretId(value) {
|
|
@@ -8382,10 +8507,11 @@ function protectedSecretId(value) {
|
|
|
8382
8507
|
}
|
|
8383
8508
|
function applyVariableChanges(rows2, changes) {
|
|
8384
8509
|
let next = rows2;
|
|
8385
|
-
for (const { key, value, keepSecret, json } of changes) {
|
|
8386
|
-
const made = () => ({ key, value: value ?? "", enabled: true, ...json && { json } });
|
|
8510
|
+
for (const { key, value, keepSecret, json, enabled } of changes) {
|
|
8511
|
+
const made = () => ({ key, value: value ?? "", enabled: enabled ?? true, ...json && { json } });
|
|
8387
8512
|
const first = next.findIndex((row) => row.key === key);
|
|
8388
|
-
if (value !== null && keepSecret && next.some((row) => row.key === key && SECRET_REFERENCE.test(row.value)))
|
|
8513
|
+
if (value !== null && keepSecret && next.some((row) => row.key === key && SECRET_REFERENCE.test(row.value)))
|
|
8514
|
+
continue;
|
|
8389
8515
|
if (value === null) {
|
|
8390
8516
|
next = next.filter((row) => row.key !== key);
|
|
8391
8517
|
} else if (first === -1) {
|
|
@@ -8401,7 +8527,8 @@ function variablesByName(rows2) {
|
|
|
8401
8527
|
}
|
|
8402
8528
|
function scriptVariables(rows2) {
|
|
8403
8529
|
const byName = /* @__PURE__ */ new Map();
|
|
8404
|
-
for (const row of rows2)
|
|
8530
|
+
for (const row of rows2)
|
|
8531
|
+
if (row.enabled && row.key) byName.set(row.key, row.json ? [row.key, row.value, true] : [row.key, row.value]);
|
|
8405
8532
|
return [...byName.values()];
|
|
8406
8533
|
}
|
|
8407
8534
|
|
|
@@ -8505,7 +8632,8 @@ function validateVaultFile(value) {
|
|
|
8505
8632
|
if (typeof id !== "string" || !id) fail(`${path}.id`, "Value must be a non-empty string.");
|
|
8506
8633
|
if (typeof field2(secret, "name") !== "string") fail(`${path}.name`, "Value must be a string.");
|
|
8507
8634
|
const environmentId = field2(secret, "environmentId");
|
|
8508
|
-
if (environmentId !== void 0 && typeof environmentId !== "string")
|
|
8635
|
+
if (environmentId !== void 0 && typeof environmentId !== "string")
|
|
8636
|
+
fail(`${path}.environmentId`, "Value must be a string.");
|
|
8509
8637
|
const envelope = field2(secret, "envelope");
|
|
8510
8638
|
if (typeof envelope !== "object" || envelope === null) {
|
|
8511
8639
|
fail(`${path}.envelope`, "Value must be an object.");
|
|
@@ -8565,6 +8693,12 @@ var VariableResolver = class {
|
|
|
8565
8693
|
unresolved = /* @__PURE__ */ new Set();
|
|
8566
8694
|
/** Ids of secrets that were referenced and not given. */
|
|
8567
8695
|
lockedSecrets = /* @__PURE__ */ new Set();
|
|
8696
|
+
/**
|
|
8697
|
+
* Each variable whose value was a secret that was given, by name, with the
|
|
8698
|
+
* scope it was found in. A reference written into a text itself is named
|
|
8699
|
+
* as written, with no scope.
|
|
8700
|
+
*/
|
|
8701
|
+
vaulted = /* @__PURE__ */ new Map();
|
|
8568
8702
|
scopes;
|
|
8569
8703
|
/** `text` with its variables replaced. Throws `VariableNestingError`. */
|
|
8570
8704
|
resolve(text3) {
|
|
@@ -8577,7 +8711,8 @@ var VariableResolver = class {
|
|
|
8577
8711
|
...this.tokensOf(row.value, "header", `header-${index}-value`)
|
|
8578
8712
|
]);
|
|
8579
8713
|
const body3 = (payload.body ?? []).flatMap((text3, index) => this.tokensOf(text3, "body", `body-${index}`));
|
|
8580
|
-
|
|
8714
|
+
const auth3 = (payload.auth ?? []).flatMap((text3, index) => this.tokensOf(text3, "auth", `auth-${index}`));
|
|
8715
|
+
return [...this.tokensOf(payload.url ?? "", "url", "endpoint"), ...header, ...body3, ...auth3];
|
|
8581
8716
|
}
|
|
8582
8717
|
tokensOf(text3, location, field4) {
|
|
8583
8718
|
const names = new Set([...text3.matchAll(PLACEHOLDER)].map((match) => match[1].trim()));
|
|
@@ -8588,6 +8723,7 @@ var VariableResolver = class {
|
|
|
8588
8723
|
if (DYNAMIC.has(name)) return { source: "dynamic" };
|
|
8589
8724
|
const found = this.find(name);
|
|
8590
8725
|
if (!found) return { source: "missing" };
|
|
8726
|
+
if (SECRET_REFERENCE.test(found.value)) return { source: found.scope, vaulted: true };
|
|
8591
8727
|
try {
|
|
8592
8728
|
return { source: found.scope, value: this.expand(found.value, [name]) };
|
|
8593
8729
|
} catch (error) {
|
|
@@ -8614,6 +8750,8 @@ var VariableResolver = class {
|
|
|
8614
8750
|
return written;
|
|
8615
8751
|
}
|
|
8616
8752
|
this.taint.add(plaintext);
|
|
8753
|
+
const holder = chain.at(-1);
|
|
8754
|
+
this.vaulted.set(holder ?? written, holder === void 0 ? void 0 : this.find(holder)?.scope);
|
|
8617
8755
|
return plaintext;
|
|
8618
8756
|
}
|
|
8619
8757
|
const dynamic = DYNAMIC.get(name);
|
|
@@ -8644,6 +8782,80 @@ function withJarCookies(headers2, cookies) {
|
|
|
8644
8782
|
return headers2.map((header, index) => index === at ? [header[0], `${header[1]}; ${cookieHeader(added2)}`] : header);
|
|
8645
8783
|
}
|
|
8646
8784
|
|
|
8785
|
+
// ../core/src/send/record.ts
|
|
8786
|
+
function formatDuration(ms) {
|
|
8787
|
+
if (ms === void 0 || ms === null || !(ms >= 0)) return "\u2014";
|
|
8788
|
+
if (ms > 0 && ms < 0.5) return "<1 ms";
|
|
8789
|
+
if (Math.round(ms) < 1e3) return `${Math.round(ms)} ms`;
|
|
8790
|
+
return `${(ms / 1e3).toFixed(1)} s`;
|
|
8791
|
+
}
|
|
8792
|
+
var isFields = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8793
|
+
var SCOPE_NAMES = VARIABLE_SCOPES;
|
|
8794
|
+
var FAILURE_KINDS = ["network", "timeout", "aborted", "bridge"];
|
|
8795
|
+
function recordProblems(value, path) {
|
|
8796
|
+
const problems = [];
|
|
8797
|
+
const object = (at, held, fields) => {
|
|
8798
|
+
if (!isFields(held)) {
|
|
8799
|
+
problems.push(`${at}: Value must be an object.`);
|
|
8800
|
+
return;
|
|
8801
|
+
}
|
|
8802
|
+
for (const key of Object.keys(held))
|
|
8803
|
+
if (!Object.hasOwn(fields, key)) problems.push(`${at}.${key}: The app writes no such field.`);
|
|
8804
|
+
for (const [key, check] of Object.entries(fields)) {
|
|
8805
|
+
const field4 = Object.hasOwn(held, key) ? held[key] : void 0;
|
|
8806
|
+
if (!check(field4, `${at}.${key}`)) problems.push(`${at}.${key}: Value is not one the app writes.`);
|
|
8807
|
+
}
|
|
8808
|
+
};
|
|
8809
|
+
const optional = (check) => (field4, where) => field4 === void 0 || check(field4, where);
|
|
8810
|
+
const text3 = (field4) => typeof field4 === "string";
|
|
8811
|
+
const yes = (field4) => field4 === true;
|
|
8812
|
+
const flag2 = (field4) => typeof field4 === "boolean";
|
|
8813
|
+
const texts = (field4) => Array.isArray(field4) && field4.every(text3);
|
|
8814
|
+
const nested = (fields) => (field4, where) => {
|
|
8815
|
+
object(where, field4, fields);
|
|
8816
|
+
return true;
|
|
8817
|
+
};
|
|
8818
|
+
object(path, value, {
|
|
8819
|
+
status: optional(
|
|
8820
|
+
nested({
|
|
8821
|
+
code: (field4) => Number.isInteger(field4) && field4 >= 0 && field4 <= 999,
|
|
8822
|
+
text: text3
|
|
8823
|
+
})
|
|
8824
|
+
),
|
|
8825
|
+
durationMs: (field4) => typeof field4 === "number" && Number.isFinite(field4) && field4 >= 0,
|
|
8826
|
+
route: (field4) => field4 === "direct" || field4 === "bridge",
|
|
8827
|
+
unpaired: optional(yes),
|
|
8828
|
+
host: text3,
|
|
8829
|
+
secrets: (field4, where) => {
|
|
8830
|
+
if (!Array.isArray(field4)) return false;
|
|
8831
|
+
field4.forEach(
|
|
8832
|
+
(secret, index) => object(`${where}[${index}]`, secret, {
|
|
8833
|
+
name: text3,
|
|
8834
|
+
scope: optional((scope) => typeof scope === "string" && SCOPE_NAMES.includes(scope)),
|
|
8835
|
+
holder: optional(text3)
|
|
8836
|
+
})
|
|
8837
|
+
);
|
|
8838
|
+
return true;
|
|
8839
|
+
},
|
|
8840
|
+
masked: nested({
|
|
8841
|
+
address: optional(yes),
|
|
8842
|
+
headers: texts,
|
|
8843
|
+
body: optional(yes),
|
|
8844
|
+
responseHeaders: texts,
|
|
8845
|
+
responseBody: optional(yes)
|
|
8846
|
+
}),
|
|
8847
|
+
kept: nested({ response: flag2, body: flag2, quoteLeftOut: optional(yes) }),
|
|
8848
|
+
failure: optional(
|
|
8849
|
+
nested({
|
|
8850
|
+
kind: (field4) => typeof field4 === "string" && FAILURE_KINDS.includes(field4),
|
|
8851
|
+
reported: text3,
|
|
8852
|
+
summary: text3
|
|
8853
|
+
})
|
|
8854
|
+
)
|
|
8855
|
+
});
|
|
8856
|
+
return problems;
|
|
8857
|
+
}
|
|
8858
|
+
|
|
8647
8859
|
// ../core/src/transport/sse.ts
|
|
8648
8860
|
var SseParser = class {
|
|
8649
8861
|
rest = "";
|
|
@@ -8716,8 +8928,12 @@ async function withDeadline(options, run) {
|
|
|
8716
8928
|
return await run(controller.signal, () => clearTimeout(timer));
|
|
8717
8929
|
} catch (cause) {
|
|
8718
8930
|
if (cause instanceof TransportError) throw cause;
|
|
8719
|
-
if (timedOut)
|
|
8720
|
-
|
|
8931
|
+
if (timedOut)
|
|
8932
|
+
throw new TransportError(
|
|
8933
|
+
"timeout",
|
|
8934
|
+
`No response within ${formatDuration(options.timeoutMs)}, the request timeout.`
|
|
8935
|
+
);
|
|
8936
|
+
if (options.signal.aborted) throw new TransportError("aborted", "You stopped the request.");
|
|
8721
8937
|
throw new TransportError("network", networkMessage(cause));
|
|
8722
8938
|
} finally {
|
|
8723
8939
|
clearTimeout(timer);
|
|
@@ -8768,7 +8984,8 @@ async function readEventStream(response, signal, onEvent) {
|
|
|
8768
8984
|
kept.set(chunk.value.subarray(0, room), size);
|
|
8769
8985
|
size += Math.min(room, chunk.value.byteLength);
|
|
8770
8986
|
if (chunk.value.byteLength > room) truncated = true;
|
|
8771
|
-
for (const event of parser.push(decoder.decode(chunk.value, { stream: true })))
|
|
8987
|
+
for (const event of parser.push(decoder.decode(chunk.value, { stream: true })))
|
|
8988
|
+
onEvent({ ...event, timeMs: Date.now() - started });
|
|
8772
8989
|
}
|
|
8773
8990
|
} catch (error) {
|
|
8774
8991
|
if (!signal.aborted) throw error;
|
|
@@ -9974,7 +10191,8 @@ var isVmHash = (name) => Object.hasOwn(HASHES, name);
|
|
|
9974
10191
|
var VM_ENCODINGS = ["utf8", "hex", "base64"];
|
|
9975
10192
|
function fromWords(json) {
|
|
9976
10193
|
const list4 = JSON.parse(json);
|
|
9977
|
-
if (!Array.isArray(list4) || !list4.every((entry) => typeof entry === "number"))
|
|
10194
|
+
if (!Array.isArray(list4) || !list4.every((entry) => typeof entry === "number"))
|
|
10195
|
+
throw new TypeError("Expected a WordArray.");
|
|
9978
10196
|
const [length, ...words] = list4;
|
|
9979
10197
|
const bytes = new Uint8Array(Math.max(0, Math.min(length, words.length * 4)));
|
|
9980
10198
|
for (let i = 0; i < bytes.length; i++) bytes[i] = words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
|
|
@@ -9998,7 +10216,10 @@ function vmHmac(name, keyFormat, key, format, payload) {
|
|
|
9998
10216
|
}
|
|
9999
10217
|
function vmEncode(encoding, text3) {
|
|
10000
10218
|
if (encoding === "utf8") return toWords(new TextEncoder().encode(text3));
|
|
10001
|
-
if (encoding === "hex")
|
|
10219
|
+
if (encoding === "hex")
|
|
10220
|
+
return toWords(
|
|
10221
|
+
Uint8Array.from({ length: text3.length >>> 1 }, (_, i) => parseInt(text3.slice(i * 2, i * 2 + 2), 16) || 0)
|
|
10222
|
+
);
|
|
10002
10223
|
return toWords(Uint8Array.from(atob(text3.replace(/=+$/, "")), (char) => char.charCodeAt(0)));
|
|
10003
10224
|
}
|
|
10004
10225
|
function vmDecode(encoding, words) {
|
|
@@ -10014,7 +10235,9 @@ function vmDecode(encoding, words) {
|
|
|
10014
10235
|
}
|
|
10015
10236
|
function vmRandom(count) {
|
|
10016
10237
|
if (!Number.isInteger(count) || count < 0 || count > 65536) throw new RangeError("Expected up to 65536 bytes.");
|
|
10017
|
-
return Array.from(crypto.getRandomValues(new Uint8Array(count)), (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
10238
|
+
return Array.from(crypto.getRandomValues(new Uint8Array(count)), (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
10239
|
+
""
|
|
10240
|
+
);
|
|
10018
10241
|
}
|
|
10019
10242
|
|
|
10020
10243
|
// ../core/src/scripting/host.ts
|
|
@@ -10039,7 +10262,10 @@ var SCRIPT_LIMITS = {
|
|
|
10039
10262
|
var WASM_PAGE = 65536;
|
|
10040
10263
|
var ENGINE_INITIAL_PAGES = 256;
|
|
10041
10264
|
function scriptMemory(limits = SCRIPT_LIMITS) {
|
|
10042
|
-
return {
|
|
10265
|
+
return {
|
|
10266
|
+
initial: ENGINE_INITIAL_PAGES,
|
|
10267
|
+
maximum: Math.max(ENGINE_INITIAL_PAGES, Math.floor(limits.memoryBytes / WASM_PAGE))
|
|
10268
|
+
};
|
|
10043
10269
|
}
|
|
10044
10270
|
var LOG_TRUNCATED = "[console output truncated]";
|
|
10045
10271
|
function emptyChanges() {
|
|
@@ -10054,9 +10280,30 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
|
|
|
10054
10280
|
const scope = (rows2 = []) => new Map(rows2.map(([name, value]) => [name, value]));
|
|
10055
10281
|
const marked = (rows2 = []) => new Set(rows2.filter(([, value, json]) => json === true && isJsonText(value)).map(([name]) => name));
|
|
10056
10282
|
const state = {
|
|
10057
|
-
values: {
|
|
10058
|
-
|
|
10059
|
-
|
|
10283
|
+
values: {
|
|
10284
|
+
local: scope(),
|
|
10285
|
+
data: scope(context.data),
|
|
10286
|
+
environment: scope(context.environment),
|
|
10287
|
+
folder: scope(context.folder),
|
|
10288
|
+
collection: scope(context.collection),
|
|
10289
|
+
global: scope(context.globals)
|
|
10290
|
+
},
|
|
10291
|
+
json: {
|
|
10292
|
+
local: marked(),
|
|
10293
|
+
data: marked(context.data),
|
|
10294
|
+
environment: marked(context.environment),
|
|
10295
|
+
folder: marked(context.folder),
|
|
10296
|
+
collection: marked(context.collection),
|
|
10297
|
+
global: marked(context.globals)
|
|
10298
|
+
},
|
|
10299
|
+
changes: {
|
|
10300
|
+
local: /* @__PURE__ */ new Map(),
|
|
10301
|
+
data: /* @__PURE__ */ new Map(),
|
|
10302
|
+
environment: /* @__PURE__ */ new Map(),
|
|
10303
|
+
folder: /* @__PURE__ */ new Map(),
|
|
10304
|
+
collection: /* @__PURE__ */ new Map(),
|
|
10305
|
+
global: /* @__PURE__ */ new Map()
|
|
10306
|
+
},
|
|
10060
10307
|
sends: 0,
|
|
10061
10308
|
inFlight: 0,
|
|
10062
10309
|
answers: [],
|
|
@@ -10066,7 +10313,11 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
|
|
|
10066
10313
|
tests: [],
|
|
10067
10314
|
timers: /* @__PURE__ */ new Map()
|
|
10068
10315
|
};
|
|
10069
|
-
const changed = (name) => [...state.changes[name]].map(([key, value]) => ({
|
|
10316
|
+
const changed = (name) => [...state.changes[name]].map(([key, value]) => ({
|
|
10317
|
+
key,
|
|
10318
|
+
value,
|
|
10319
|
+
...value !== null && state.json[name].has(key) && { json: true }
|
|
10320
|
+
}));
|
|
10070
10321
|
const result = (error, limit) => ({
|
|
10071
10322
|
logs: state.logs,
|
|
10072
10323
|
changes: { environment: changed("environment"), collection: changed("collection"), global: changed("global") },
|
|
@@ -10086,7 +10337,8 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
|
|
|
10086
10337
|
try {
|
|
10087
10338
|
failure = await evaluate(vm, runtime, source, context, state, deadline, limits, services);
|
|
10088
10339
|
} catch (error) {
|
|
10089
|
-
if (error instanceof RangeError || error instanceof Error && error.name === "InternalError")
|
|
10340
|
+
if (error instanceof RangeError || error instanceof Error && error.name === "InternalError")
|
|
10341
|
+
return result(void 0, "stack");
|
|
10090
10342
|
throw error;
|
|
10091
10343
|
}
|
|
10092
10344
|
vm.dispose();
|
|
@@ -10130,7 +10382,8 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10130
10382
|
};
|
|
10131
10383
|
const charge = (...written) => {
|
|
10132
10384
|
state.written += written.reduce((sum, part) => sum + part.length, 0);
|
|
10133
|
-
if (state.written > limits.maxOutputBytes)
|
|
10385
|
+
if (state.written > limits.maxOutputBytes)
|
|
10386
|
+
throw new RangeError("Script wrote too many test results and variables.");
|
|
10134
10387
|
};
|
|
10135
10388
|
const scopeOf = (handle) => {
|
|
10136
10389
|
const name = text3(handle);
|
|
@@ -10153,7 +10406,9 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10153
10406
|
});
|
|
10154
10407
|
bind("varHas", (where, key) => lookUp(where, text3(key)) === void 0 ? vm.false : vm.true);
|
|
10155
10408
|
bind("varAll", (where) => {
|
|
10156
|
-
const of2 = (name) => [...state.values[name]].map(
|
|
10409
|
+
const of2 = (name) => [...state.values[name]].map(
|
|
10410
|
+
([key, value]) => `[${JSON.stringify(key)},${state.json[name].has(key) ? value : JSON.stringify(value)}]`
|
|
10411
|
+
);
|
|
10157
10412
|
const entries = any(where) ? [...VARIABLE_ORDER].reverse().flatMap(of2) : of2(scopeOf(where));
|
|
10158
10413
|
return vm.newString(`[${entries.join(",")}]`);
|
|
10159
10414
|
});
|
|
@@ -10219,7 +10474,8 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10219
10474
|
const id = text3(name);
|
|
10220
10475
|
const source2 = services.libraries?.get(id);
|
|
10221
10476
|
if (source2 !== void 0) return vm.newString(source2);
|
|
10222
|
-
if (VM_LIBRARIES.includes(id))
|
|
10477
|
+
if (VM_LIBRARIES.includes(id))
|
|
10478
|
+
throw new Error(`require('${id}'): write the module's name out in the script, as require('${id}')`);
|
|
10223
10479
|
return void 0;
|
|
10224
10480
|
});
|
|
10225
10481
|
const hash = (handle) => {
|
|
@@ -10234,7 +10490,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10234
10490
|
return found;
|
|
10235
10491
|
};
|
|
10236
10492
|
bind("hash", (name, format, payload) => vm.newString(vmHash(hash(name), text3(format), text3(payload))));
|
|
10237
|
-
bind(
|
|
10493
|
+
bind(
|
|
10494
|
+
"hmac",
|
|
10495
|
+
(name, keyFormat, key, format, payload) => vm.newString(vmHmac(hash(name), text3(keyFormat), text3(key), text3(format), text3(payload)))
|
|
10496
|
+
);
|
|
10238
10497
|
bind("encode", (name, value) => vm.newString(vmEncode(encoding(name), text3(value))));
|
|
10239
10498
|
bind("decode", (name, words) => vm.newString(vmDecode(encoding(name), text3(words))));
|
|
10240
10499
|
bind("random", (count) => vm.newString(vmRandom(number(count))));
|
|
@@ -10258,7 +10517,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10258
10517
|
state.answers.push([call, ok, JSON.stringify(value)]);
|
|
10259
10518
|
state.wake?.();
|
|
10260
10519
|
};
|
|
10261
|
-
const refused = (error) => ({
|
|
10520
|
+
const refused = (error) => ({
|
|
10521
|
+
name: error instanceof Error ? error.name : "Error",
|
|
10522
|
+
message: error instanceof Error ? error.message : String(error)
|
|
10523
|
+
});
|
|
10262
10524
|
bind("sendFailed", (call, name, message) => {
|
|
10263
10525
|
answer(number(call), false, { name: text3(name), message: text3(message) });
|
|
10264
10526
|
return void 0;
|
|
@@ -10267,7 +10529,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10267
10529
|
const [id, body3] = [number(call), text3(json)];
|
|
10268
10530
|
state.sends++;
|
|
10269
10531
|
if (state.sends > limits.maxSendRequests) {
|
|
10270
|
-
answer(id, false, {
|
|
10532
|
+
answer(id, false, {
|
|
10533
|
+
name: "Error",
|
|
10534
|
+
message: `pm.sendRequest: a script may make ${limits.maxSendRequests} requests, and this is one more.`
|
|
10535
|
+
});
|
|
10271
10536
|
return void 0;
|
|
10272
10537
|
}
|
|
10273
10538
|
const send = services.send;
|
|
@@ -10370,12 +10635,15 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10370
10635
|
}
|
|
10371
10636
|
}
|
|
10372
10637
|
var VARIABLE_ORDER = VARIABLE_SCOPES;
|
|
10373
|
-
var isPairs = (value) => Array.isArray(value) && value.every(
|
|
10638
|
+
var isPairs = (value) => Array.isArray(value) && value.every(
|
|
10639
|
+
(pair) => Array.isArray(pair) && pair.length === 2 && typeof pair[0] === "string" && typeof pair[1] === "string"
|
|
10640
|
+
);
|
|
10374
10641
|
function parsed(json) {
|
|
10375
10642
|
const value = JSON.parse(json);
|
|
10376
10643
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Expected a request.");
|
|
10377
10644
|
const request = value;
|
|
10378
|
-
if (typeof request["method"] !== "string" || !isHttpMethod(request["method"]))
|
|
10645
|
+
if (typeof request["method"] !== "string" || !isHttpMethod(request["method"]))
|
|
10646
|
+
throw new TypeError("The request's method is not an HTTP method.");
|
|
10379
10647
|
if (typeof request["url"] !== "string" || !request["url"].trim()) throw new TypeError("The request has no address.");
|
|
10380
10648
|
if (!isPairs(request["headers"])) throw new TypeError("The request's headers are not a list of names and values.");
|
|
10381
10649
|
return request;
|
|
@@ -10384,15 +10652,26 @@ function sendRequest(json) {
|
|
|
10384
10652
|
const request = parsed(json);
|
|
10385
10653
|
const body3 = request["body"];
|
|
10386
10654
|
if (body3 !== void 0 && typeof body3 !== "string") throw new TypeError("The request's body is not text.");
|
|
10387
|
-
return {
|
|
10655
|
+
return {
|
|
10656
|
+
method: request["method"],
|
|
10657
|
+
url: request["url"],
|
|
10658
|
+
headers: request["headers"],
|
|
10659
|
+
...body3 !== void 0 && { body: body3 }
|
|
10660
|
+
};
|
|
10388
10661
|
}
|
|
10389
10662
|
function scriptRequest(json) {
|
|
10390
10663
|
const request = parsed(json);
|
|
10391
10664
|
const body3 = request["body"];
|
|
10392
|
-
const base = {
|
|
10665
|
+
const base = {
|
|
10666
|
+
method: request["method"],
|
|
10667
|
+
url: request["url"],
|
|
10668
|
+
headers: request["headers"]
|
|
10669
|
+
};
|
|
10393
10670
|
if (body3?.mode === "raw" && typeof body3.raw === "string") return { ...base, body: { mode: "raw", raw: body3.raw } };
|
|
10394
|
-
if (body3?.mode === "urlencoded" && isPairs(body3.urlencoded))
|
|
10395
|
-
|
|
10671
|
+
if (body3?.mode === "urlencoded" && isPairs(body3.urlencoded))
|
|
10672
|
+
return { ...base, body: { mode: "urlencoded", urlencoded: body3.urlencoded } };
|
|
10673
|
+
if (body3?.mode === "none" || body3?.mode === "multipart" || body3?.mode === "binary")
|
|
10674
|
+
return { ...base, body: { mode: body3.mode } };
|
|
10396
10675
|
throw new TypeError("The request's body is not one a script can set.");
|
|
10397
10676
|
}
|
|
10398
10677
|
function property(vm, object, name) {
|
|
@@ -10414,7 +10693,14 @@ function scriptRequestOf(content) {
|
|
|
10414
10693
|
function withScriptRequest(content, request) {
|
|
10415
10694
|
const rows2 = (pairs2) => pairs2.map(([key, value]) => ({ key, value, enabled: true }));
|
|
10416
10695
|
const untouched = content.body.mode === "graphql" && request.body.mode === "raw" && request.body.raw === graphqlText(content.body.graphql?.query ?? "", content.body.graphql?.variables ?? "");
|
|
10417
|
-
const body3 = untouched ? content.body : request.body.mode === "raw" ? {
|
|
10696
|
+
const body3 = untouched ? content.body : request.body.mode === "raw" ? {
|
|
10697
|
+
...content.body,
|
|
10698
|
+
mode: "raw",
|
|
10699
|
+
raw: {
|
|
10700
|
+
language: content.body.mode === "graphql" ? "json" : content.body.raw?.language ?? "text",
|
|
10701
|
+
text: request.body.raw
|
|
10702
|
+
}
|
|
10703
|
+
} : request.body.mode === "urlencoded" ? { ...content.body, mode: "urlencoded", urlencoded: rows2(request.body.urlencoded) } : content.body;
|
|
10418
10704
|
return { ...content, method: request.method, url: request.url, headers: rows2(request.headers), body: body3 };
|
|
10419
10705
|
}
|
|
10420
10706
|
function sentScriptRequest(content, sent) {
|
|
@@ -10457,7 +10743,12 @@ async function runRequests(requests, send, options = {}) {
|
|
|
10457
10743
|
const count = options.iterations ?? (data.length || 1);
|
|
10458
10744
|
const signal = options.signal ?? new AbortController().signal;
|
|
10459
10745
|
const results = [];
|
|
10460
|
-
const report = (ended, reason) => ({
|
|
10746
|
+
const report = (ended, reason) => ({
|
|
10747
|
+
iterations: count,
|
|
10748
|
+
results,
|
|
10749
|
+
ended,
|
|
10750
|
+
...reason !== void 0 && { reason }
|
|
10751
|
+
});
|
|
10461
10752
|
let first = true;
|
|
10462
10753
|
for (let index = 0; index < count; index++) {
|
|
10463
10754
|
const iteration = { index, count, data: data[Math.min(index, data.length - 1)] ?? [] };
|
|
@@ -10479,6 +10770,35 @@ async function runRequests(requests, send, options = {}) {
|
|
|
10479
10770
|
return report("completed");
|
|
10480
10771
|
}
|
|
10481
10772
|
|
|
10773
|
+
// ../core/src/run/words.ts
|
|
10774
|
+
function runEnding(report) {
|
|
10775
|
+
switch (report.ended) {
|
|
10776
|
+
case "completed":
|
|
10777
|
+
return "The run is complete.";
|
|
10778
|
+
case "cancelled":
|
|
10779
|
+
return "You stopped the run.";
|
|
10780
|
+
case "failed":
|
|
10781
|
+
return "The run stopped at the first failure.";
|
|
10782
|
+
case "ended":
|
|
10783
|
+
return `The run was stopped. ${report.reason ?? ""}`.trim();
|
|
10784
|
+
}
|
|
10785
|
+
}
|
|
10786
|
+
function runCounts(report) {
|
|
10787
|
+
const { results, iterations } = report;
|
|
10788
|
+
const tests2 = results.flatMap((result) => result.tests);
|
|
10789
|
+
const failed = results.filter(runFailed).length;
|
|
10790
|
+
const passed = tests2.filter((test) => test.passed).length;
|
|
10791
|
+
return `${results.length} ${results.length === 1 ? "result" : "results"} in ${iterations} ${iterations === 1 ? "iteration" : "iterations"}; ${failed} failed; ${passed} of ${tests2.length} tests passed.`;
|
|
10792
|
+
}
|
|
10793
|
+
function sharedErrors(results) {
|
|
10794
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
10795
|
+
for (const { error } of results) if (error !== void 0) counts2.set(error, (counts2.get(error) ?? 0) + 1);
|
|
10796
|
+
return [...counts2].filter(([, count]) => count >= 2).sort((a, b) => b[1] - a[1]).map(([error, count]) => ({ error, count }));
|
|
10797
|
+
}
|
|
10798
|
+
function sharedErrorHeading(count) {
|
|
10799
|
+
return `${count} requests failed with the same error.`;
|
|
10800
|
+
}
|
|
10801
|
+
|
|
10482
10802
|
// ../core/src/send/wire.ts
|
|
10483
10803
|
var SECRET_PLACEHOLDER = /\{\{\s*\$secret\./;
|
|
10484
10804
|
function containsSecretPlaceholder(request) {
|
|
@@ -10682,14 +11002,17 @@ var RequestSkipped = class extends SendBlockedError {
|
|
|
10682
11002
|
scriptLogs;
|
|
10683
11003
|
nextRequest;
|
|
10684
11004
|
};
|
|
10685
|
-
var
|
|
10686
|
-
var
|
|
11005
|
+
var NO_RESPONSE_TEXT = "The request got no response. The browser does not say why: the name may not resolve, the server may have refused the connection, or it does not allow requests from this page (CORS). Send it through the Local Bridge to see which.";
|
|
11006
|
+
var OFFLINE_TEXT = "The request got no response: this browser is offline. Connect, then send it again.";
|
|
11007
|
+
var noResponseWithoutPage = (reason) => `The request got no response: ${reason}. Check the address, that the name resolves and that the server accepts connections.`;
|
|
10687
11008
|
var SECRET_PLACEHOLDER_BLOCKED = "This request refers to a vault secret that could not be read (it may have been deleted), so it was not sent. Give the variable a value again.";
|
|
10688
11009
|
function sentBodyPreview(body3) {
|
|
10689
11010
|
if (body3 === void 0 || typeof body3 === "string") return body3;
|
|
10690
11011
|
const file = (blob) => `(${blob.size} bytes${blob.type ? `, ${blob.type}` : ""})`;
|
|
10691
11012
|
if (body3 instanceof FormData) {
|
|
10692
|
-
return [...body3].map(
|
|
11013
|
+
return [...body3].map(
|
|
11014
|
+
([name, value]) => typeof value === "string" ? `${name}=${value}` : `${name}=@${value.name} ${file(value)}`
|
|
11015
|
+
).join("\n");
|
|
10693
11016
|
}
|
|
10694
11017
|
return `@file ${file(body3)}`;
|
|
10695
11018
|
}
|
|
@@ -10710,7 +11033,10 @@ function bodyText(body3) {
|
|
|
10710
11033
|
return stringifyJson(body3) ?? "";
|
|
10711
11034
|
}
|
|
10712
11035
|
function lastNext(runs) {
|
|
10713
|
-
return runs.reduce(
|
|
11036
|
+
return runs.reduce(
|
|
11037
|
+
(last, run) => run.result.nextRequest !== void 0 ? run.result.nextRequest : last,
|
|
11038
|
+
void 0
|
|
11039
|
+
);
|
|
10714
11040
|
}
|
|
10715
11041
|
function scriptsToRun(spec, kind) {
|
|
10716
11042
|
const label = kind === "pre" ? "Pre-request script" : "Post-response script";
|
|
@@ -10722,7 +11048,11 @@ function scriptsToRun(spec, kind) {
|
|
|
10722
11048
|
function scriptRows(result, name, redactor) {
|
|
10723
11049
|
if (!result) return [];
|
|
10724
11050
|
const rows2 = result.error === void 0 ? result.testResults : [...result.testResults, { label: name, passed: false, error: result.error, source: "script" }];
|
|
10725
|
-
return rows2.map((row) => ({
|
|
11051
|
+
return rows2.map((row) => ({
|
|
11052
|
+
...row,
|
|
11053
|
+
label: redactor.text(row.label),
|
|
11054
|
+
...row.error !== void 0 && { error: redactor.text(row.error) }
|
|
11055
|
+
}));
|
|
10726
11056
|
}
|
|
10727
11057
|
async function exchange(spec, ports) {
|
|
10728
11058
|
const createdAt = Date.now();
|
|
@@ -10733,7 +11063,11 @@ async function exchange(spec, ports) {
|
|
|
10733
11063
|
let undo = () => Promise.resolve();
|
|
10734
11064
|
const preScripts = spec.runScripts ? scriptsToRun(spec, "pre") : [];
|
|
10735
11065
|
if (preScripts.length) {
|
|
10736
|
-
const before = {
|
|
11066
|
+
const before = {
|
|
11067
|
+
environment: ports.environment()?.vars ?? [],
|
|
11068
|
+
global: ports.globals(),
|
|
11069
|
+
collection: spec.collection?.variables() ?? []
|
|
11070
|
+
};
|
|
10737
11071
|
const touched = emptyChanges();
|
|
10738
11072
|
const restoreSecrets = [];
|
|
10739
11073
|
const was = (rows2, names) => names.map(({ key }) => {
|
|
@@ -10741,13 +11075,27 @@ async function exchange(spec, ports) {
|
|
|
10741
11075
|
return { key, value, ...json && { json } };
|
|
10742
11076
|
});
|
|
10743
11077
|
undo = async () => {
|
|
10744
|
-
await applyChanges(
|
|
11078
|
+
await applyChanges(
|
|
11079
|
+
ports,
|
|
11080
|
+
spec,
|
|
11081
|
+
{
|
|
11082
|
+
environment: was(before.environment, touched.environment),
|
|
11083
|
+
global: was(before.global, touched.global),
|
|
11084
|
+
collection: was(before.collection, touched.collection)
|
|
11085
|
+
},
|
|
11086
|
+
[]
|
|
11087
|
+
);
|
|
10745
11088
|
for (const restore of restoreSecrets.reverse()) await restore();
|
|
10746
11089
|
};
|
|
10747
11090
|
timings.preScriptMs = 0;
|
|
10748
11091
|
for (const { name, text: text3 } of preScripts) {
|
|
10749
11092
|
const scriptStarted = performance.now();
|
|
10750
|
-
const result = await ports.runScript(
|
|
11093
|
+
const result = await ports.runScript(
|
|
11094
|
+
text3,
|
|
11095
|
+
environmentSnapshot(ports),
|
|
11096
|
+
void 0,
|
|
11097
|
+
scriptExtras(ports, spec, "prerequest", scriptRequestOf(template))
|
|
11098
|
+
);
|
|
10751
11099
|
timings.preScriptMs += Math.round(performance.now() - scriptStarted);
|
|
10752
11100
|
preRuns.push({ name, result });
|
|
10753
11101
|
const { changes } = result;
|
|
@@ -10755,13 +11103,22 @@ async function exchange(spec, ports) {
|
|
|
10755
11103
|
for (const scope of ["environment", "global", "collection"]) touched[scope].push(...changes[scope]);
|
|
10756
11104
|
if (result.error !== void 0) {
|
|
10757
11105
|
const nothing = new Redactor([]);
|
|
10758
|
-
throw new PreRequestScriptError(
|
|
11106
|
+
throw new PreRequestScriptError(
|
|
11107
|
+
preRuns.flatMap((run) => scriptRows(run.result, run.name, nothing)),
|
|
11108
|
+
[...preRuns.flatMap((run) => run.result.logs), ...notes],
|
|
11109
|
+
timings,
|
|
11110
|
+
name
|
|
11111
|
+
);
|
|
10759
11112
|
}
|
|
10760
11113
|
if (result.request) template = withScriptRequest(template, result.request);
|
|
10761
11114
|
}
|
|
10762
11115
|
if (spec.iteration && preRuns.some((run) => run.result.skipRequest)) {
|
|
10763
11116
|
const nothing = new Redactor([]);
|
|
10764
|
-
throw new RequestSkipped(
|
|
11117
|
+
throw new RequestSkipped(
|
|
11118
|
+
preRuns.flatMap((run) => scriptRows(run.result, run.name, nothing)),
|
|
11119
|
+
[...preRuns.flatMap((run) => run.result.logs), ...notes],
|
|
11120
|
+
lastNext(preRuns)
|
|
11121
|
+
);
|
|
10765
11122
|
}
|
|
10766
11123
|
}
|
|
10767
11124
|
let request;
|
|
@@ -10782,14 +11139,19 @@ async function exchange(spec, ports) {
|
|
|
10782
11139
|
const jar = ports.cookies;
|
|
10783
11140
|
const cookies = jar && {
|
|
10784
11141
|
for: (url) => {
|
|
10785
|
-
const found = jar.for(url).filter(
|
|
11142
|
+
const found = jar.for(url).filter(
|
|
11143
|
+
(cookie) => !containsSecretPlaceholder({ url: "", headers: [["Cookie", `${cookie.name}=${cookie.value}`]] })
|
|
11144
|
+
);
|
|
10786
11145
|
sentCookies ??= found;
|
|
10787
11146
|
for (const cookie of found) cookieValues.add(cookie.value);
|
|
10788
11147
|
return found;
|
|
10789
11148
|
},
|
|
10790
11149
|
store: (url, setCookies) => void jar.store(url, setCookies, request.secrets)
|
|
10791
11150
|
};
|
|
10792
|
-
const policy = {
|
|
11151
|
+
const policy = {
|
|
11152
|
+
route: spec.route ?? ports.transport.route(),
|
|
11153
|
+
...spec.askRoute && { ask: spec.askRoute }
|
|
11154
|
+
};
|
|
10793
11155
|
const transmit = async () => {
|
|
10794
11156
|
startedAt = performance.now();
|
|
10795
11157
|
sentCookies = void 0;
|
|
@@ -10830,7 +11192,7 @@ async function exchange(spec, ports) {
|
|
|
10830
11192
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
10831
11193
|
timings.requestMs = durationMs;
|
|
10832
11194
|
ports.watch?.received(request.url);
|
|
10833
|
-
const shaped = outcome instanceof TransportError ? shapeFailure(outcome,
|
|
11195
|
+
const shaped = outcome instanceof TransportError ? shapeFailure(outcome, ports.page, ports.online?.()) : shapeResponse(outcome, ports.page);
|
|
10834
11196
|
const redactor = new Redactor([...request.secrets, ...request.credentials, ...cookieValues]);
|
|
10835
11197
|
const secrets = new Redactor(request.secrets);
|
|
10836
11198
|
const post = await runPostScriptAndAssertions(
|
|
@@ -10848,7 +11210,14 @@ async function exchange(spec, ports) {
|
|
|
10848
11210
|
notes,
|
|
10849
11211
|
secrets,
|
|
10850
11212
|
timings,
|
|
10851
|
-
{
|
|
11213
|
+
{
|
|
11214
|
+
statusCode: shaped.response.statusCode ?? 0,
|
|
11215
|
+
statusText: shaped.response.statusText ?? "",
|
|
11216
|
+
body: shaped.body,
|
|
11217
|
+
headers: shaped.headers,
|
|
11218
|
+
durationMs,
|
|
11219
|
+
sizeBytes: outcome instanceof TransportError ? void 0 : outcome.sizes.decoded
|
|
11220
|
+
}
|
|
10852
11221
|
);
|
|
10853
11222
|
const runs = [...preRuns, ...post.scripts];
|
|
10854
11223
|
const testResults = [...runs.flatMap((run) => scriptRows(run.result, run.name, redactor)), ...post.assertions];
|
|
@@ -10868,7 +11237,10 @@ async function exchange(spec, ports) {
|
|
|
10868
11237
|
...preview !== void 0 && { bodyPreview: keep(preview).text }
|
|
10869
11238
|
},
|
|
10870
11239
|
route: outcome instanceof TransportError ? policy.took ?? ports.transport.first(policy.route) : outcome.route,
|
|
10871
|
-
durationMs
|
|
11240
|
+
durationMs,
|
|
11241
|
+
...spec.info && {
|
|
11242
|
+
sentFrom: { name: redactor.text(spec.info.requestName), path: spec.info.path.map((name) => redactor.text(name)) }
|
|
11243
|
+
}
|
|
10872
11244
|
};
|
|
10873
11245
|
if (!(outcome instanceof TransportError)) {
|
|
10874
11246
|
history.response = {
|
|
@@ -10882,7 +11254,25 @@ async function exchange(spec, ports) {
|
|
|
10882
11254
|
history.error = redactor.text(shaped.historyError);
|
|
10883
11255
|
}
|
|
10884
11256
|
const nextRequest = lastNext(runs);
|
|
11257
|
+
const record = recordOf(history, {
|
|
11258
|
+
sentHeaders: withJarCookies(request.headers, sentCookies ?? []),
|
|
11259
|
+
sentUrl: request.url,
|
|
11260
|
+
sentPreview: preview,
|
|
11261
|
+
received: outcome instanceof TransportError ? void 0 : { headers: shaped.headerList, body: shaped.bodyText },
|
|
11262
|
+
vaulted: (request.vaulted ?? []).map((secret) => ({
|
|
11263
|
+
...secret,
|
|
11264
|
+
...secret.scope && { holder: holderOf(secret.scope, ports, spec) }
|
|
11265
|
+
})),
|
|
11266
|
+
unpaired: outcome instanceof TransportError && history.route === "direct" && policy.unpaired === true,
|
|
11267
|
+
failure: outcome instanceof TransportError ? {
|
|
11268
|
+
kind: outcome.kind,
|
|
11269
|
+
reported: redactor.text(outcome.message),
|
|
11270
|
+
summary: redactor.text(firstSentence(shaped.response.errorText))
|
|
11271
|
+
} : void 0
|
|
11272
|
+
});
|
|
11273
|
+
history.record = record;
|
|
10885
11274
|
return {
|
|
11275
|
+
record,
|
|
10886
11276
|
durationMs,
|
|
10887
11277
|
timings,
|
|
10888
11278
|
...!(outcome instanceof TransportError) && outcome.timings && { relayTimings: outcome.timings },
|
|
@@ -10894,11 +11284,44 @@ async function exchange(spec, ports) {
|
|
|
10894
11284
|
...nextRequest !== void 0 && { nextRequest }
|
|
10895
11285
|
};
|
|
10896
11286
|
}
|
|
10897
|
-
function
|
|
11287
|
+
function firstSentence(text3) {
|
|
11288
|
+
const match = /^.*?[.!?](?=\s|$)/s.exec(text3.trim());
|
|
11289
|
+
return (match ? match[0] : text3).trim();
|
|
11290
|
+
}
|
|
11291
|
+
function holderOf(scope, ports, spec) {
|
|
11292
|
+
if (scope === "environment") {
|
|
11293
|
+
const name = ports.environment()?.name;
|
|
11294
|
+
return name === void 0 ? void 0 : `environment "${name}"`;
|
|
11295
|
+
}
|
|
11296
|
+
if (scope === "collection") return spec.inherited?.[0]?.name;
|
|
11297
|
+
return void 0;
|
|
11298
|
+
}
|
|
11299
|
+
function recordOf(history, raw) {
|
|
11300
|
+
const changed = (kept, sent) => kept.filter(([, value], index) => value !== sent[index]?.[1]).map(([name]) => name);
|
|
11301
|
+
const response = history.response;
|
|
11302
|
+
const keptUrl = history.sent.url;
|
|
11303
|
+
return {
|
|
11304
|
+
...response && { status: { code: response.status, text: response.statusText } },
|
|
11305
|
+
durationMs: history.durationMs ?? 0,
|
|
11306
|
+
route: history.route,
|
|
11307
|
+
host: URL.parse(keptUrl)?.host || keptUrl,
|
|
11308
|
+
secrets: raw.vaulted,
|
|
11309
|
+
masked: {
|
|
11310
|
+
...keptUrl !== raw.sentUrl && { address: true },
|
|
11311
|
+
headers: [...new Set(changed(history.sent.headers, raw.sentHeaders))],
|
|
11312
|
+
...raw.sentPreview !== void 0 && history.sent.bodyPreview !== raw.sentPreview.slice(0, HISTORY_BODY_LIMIT) && { body: true },
|
|
11313
|
+
responseHeaders: response && raw.received ? [...new Set(changed(response.headers, raw.received.headers))] : [],
|
|
11314
|
+
...response?.body && raw.received?.body !== void 0 && response.body.text !== raw.received.body.slice(0, HISTORY_BODY_LIMIT) && { responseBody: true }
|
|
11315
|
+
},
|
|
11316
|
+
kept: { response: response !== void 0, body: response?.body !== void 0 },
|
|
11317
|
+
...raw.unpaired && { unpaired: true },
|
|
11318
|
+
...raw.failure && { failure: raw.failure }
|
|
11319
|
+
};
|
|
11320
|
+
}
|
|
11321
|
+
function shapeResponse(envelope, page) {
|
|
10898
11322
|
const body3 = decodeEnvelope(envelope);
|
|
10899
11323
|
const isError = envelope.status < 200 || envelope.status >= 300;
|
|
10900
|
-
const
|
|
10901
|
-
const shown = isError ? body3 ?? failureText : body3;
|
|
11324
|
+
const shown = body3;
|
|
10902
11325
|
const bodyIsJson = isJsonPayload(shown);
|
|
10903
11326
|
const text3 = bodyIsJson ? serializeJsonPayload(shown) : stringifyPayload(shown);
|
|
10904
11327
|
const header = (name) => envelope.headers.find(([key]) => key.toLowerCase() === name)?.[1];
|
|
@@ -10908,7 +11331,6 @@ function shapeResponse(envelope, url, page) {
|
|
|
10908
11331
|
headers: Object.fromEntries(envelope.headers),
|
|
10909
11332
|
headerList: envelope.headers,
|
|
10910
11333
|
bodyText: body3 === void 0 || body3 === null || body3 instanceof BinaryBody ? void 0 : text3,
|
|
10911
|
-
historyError: isError ? failureText : void 0,
|
|
10912
11334
|
response: {
|
|
10913
11335
|
isError,
|
|
10914
11336
|
statusCode: envelope.status,
|
|
@@ -10917,34 +11339,36 @@ function shapeResponse(envelope, url, page) {
|
|
|
10917
11339
|
dataText: isError ? "" : text3,
|
|
10918
11340
|
errorText: isError ? text3 : "",
|
|
10919
11341
|
headersView: envelope.headers.map(([name, value]) => ({ name, value })).sort((a, b) => a.name.localeCompare(b.name)),
|
|
10920
|
-
contentLength: Number.isFinite(contentLength) ? contentLength :
|
|
11342
|
+
contentLength: Number.isFinite(contentLength) ? contentLength : envelope.sizes.decoded,
|
|
10921
11343
|
binary: body3 instanceof BinaryBody ? body3 : void 0,
|
|
10922
11344
|
redirectedTo: envelope.redirected ? envelope.finalUrl : void 0,
|
|
10923
11345
|
route: envelope.route,
|
|
10924
11346
|
contentType: header("content-type"),
|
|
10925
|
-
// Outside a page nothing is withheld: no browser stands between.
|
|
10926
|
-
|
|
11347
|
+
// Outside a page nothing is withheld: no browser stands between. A wildcard exposes every header
|
|
11348
|
+
// but Set-Cookie to a request without credentials, which every direct send is (U-173).
|
|
11349
|
+
headersLimited: page !== void 0 && envelope.route === "direct" && URL.parse(envelope.finalUrl, page)?.origin !== URL.parse(page)?.origin && !(header("access-control-expose-headers") ?? "").split(",").some((name) => name.trim() === "*"),
|
|
10927
11350
|
...envelope.stream && { stream: envelope.stream },
|
|
10928
11351
|
...envelope.redirects && { redirects: envelope.redirects },
|
|
10929
11352
|
...envelope.tlsInsecure && { tlsInsecure: true }
|
|
10930
11353
|
}
|
|
10931
11354
|
};
|
|
10932
11355
|
}
|
|
10933
|
-
function shapeFailure(failure,
|
|
11356
|
+
function shapeFailure(failure, page, online) {
|
|
10934
11357
|
const network = failure.kind === "network";
|
|
10935
|
-
const
|
|
11358
|
+
const said2 = network || failure.kind === "bridge";
|
|
11359
|
+
const errorText = !network ? failure.message : page === void 0 ? noResponseWithoutPage(failure.message) : online === false ? OFFLINE_TEXT : NO_RESPONSE_TEXT;
|
|
10936
11360
|
return {
|
|
10937
|
-
body:
|
|
11361
|
+
body: said2 ? void 0 : failure.message,
|
|
10938
11362
|
headers: {},
|
|
10939
11363
|
headerList: [],
|
|
10940
|
-
historyError: network ? `
|
|
11364
|
+
historyError: network ? page === void 0 ? `No response: ${failure.message}.` : online === false ? "No response: this browser is offline." : "No response. The browser does not say why." : failure.message,
|
|
10941
11365
|
response: {
|
|
10942
11366
|
isError: true,
|
|
10943
|
-
statusCode,
|
|
10944
|
-
statusText:
|
|
11367
|
+
statusCode: void 0,
|
|
11368
|
+
statusText: "",
|
|
10945
11369
|
bodyIsJson: false,
|
|
10946
11370
|
dataText: "",
|
|
10947
|
-
errorText
|
|
11371
|
+
errorText,
|
|
10948
11372
|
headersView: []
|
|
10949
11373
|
}
|
|
10950
11374
|
};
|
|
@@ -10953,13 +11377,21 @@ async function runPostScriptAndAssertions(ports, spec, sent, notes, variables2,
|
|
|
10953
11377
|
const scripts2 = [];
|
|
10954
11378
|
for (const { name, text: text3 } of spec.runScripts ? scriptsToRun(spec, "post") : []) {
|
|
10955
11379
|
const scriptStarted = performance.now();
|
|
10956
|
-
const result = await ports.runScript(
|
|
11380
|
+
const result = await ports.runScript(
|
|
11381
|
+
text3,
|
|
11382
|
+
environmentSnapshot(ports),
|
|
11383
|
+
response,
|
|
11384
|
+
scriptExtras(ports, spec, "test", sent)
|
|
11385
|
+
);
|
|
10957
11386
|
timings.postScriptMs = (timings.postScriptMs ?? 0) + Math.round(performance.now() - scriptStarted);
|
|
10958
11387
|
scripts2.push({ name, result });
|
|
10959
11388
|
await applyChanges(ports, spec, result.changes, notes, variables2);
|
|
10960
11389
|
}
|
|
10961
11390
|
const { statusCode, body: body3, headers: headers2, durationMs } = response;
|
|
10962
|
-
return {
|
|
11391
|
+
return {
|
|
11392
|
+
scripts: scripts2,
|
|
11393
|
+
assertions: spec.tests.length ? runAssertions(spec.tests, { statusCode, body: body3, headers: headers2, durationMs }) : []
|
|
11394
|
+
};
|
|
10963
11395
|
}
|
|
10964
11396
|
function environmentSnapshot(ports) {
|
|
10965
11397
|
return scriptVariables(ports.environment()?.vars ?? []);
|
|
@@ -10972,17 +11404,31 @@ function scriptExtras(ports, spec, eventName, request) {
|
|
|
10972
11404
|
...spec.iteration && { data: scriptVariables(spec.iteration.data) },
|
|
10973
11405
|
collection: scriptVariables(spec.collection?.variables() ?? []),
|
|
10974
11406
|
request,
|
|
10975
|
-
info: {
|
|
11407
|
+
info: {
|
|
11408
|
+
eventName,
|
|
11409
|
+
requestName: spec.info?.requestName ?? "",
|
|
11410
|
+
requestId: spec.info?.requestId ?? "",
|
|
11411
|
+
...spec.iteration && { iteration: spec.iteration.index, iterationCount: spec.iteration.count }
|
|
11412
|
+
},
|
|
10976
11413
|
send: (request2) => sendForScript(ports, request2, spec.signal, spec.route)
|
|
10977
11414
|
};
|
|
10978
11415
|
}
|
|
10979
11416
|
async function sendForScript(ports, request, signal, route = ports.transport.route()) {
|
|
10980
11417
|
if (containsSecretPlaceholder(request)) {
|
|
10981
|
-
throw Object.assign(
|
|
11418
|
+
throw Object.assign(
|
|
11419
|
+
new Error(
|
|
11420
|
+
"pm.sendRequest with a vault secret is not supported \u2014 see docs/postman-compatibility.md#pm-sendrequest"
|
|
11421
|
+
),
|
|
11422
|
+
{ name: "WayfarerUnsupportedError" }
|
|
11423
|
+
);
|
|
10982
11424
|
}
|
|
10983
11425
|
const startedAt = performance.now();
|
|
10984
|
-
const envelope = await ports.transport.send(
|
|
10985
|
-
|
|
11426
|
+
const envelope = await ports.transport.send(
|
|
11427
|
+
request,
|
|
11428
|
+
{ signal: signal ?? new AbortController().signal, timeoutMs: ports.timeoutMs() },
|
|
11429
|
+
{ route }
|
|
11430
|
+
);
|
|
11431
|
+
const shaped = shapeResponse(envelope, ports.page);
|
|
10986
11432
|
return scriptResponse({
|
|
10987
11433
|
statusCode: envelope.status,
|
|
10988
11434
|
statusText: envelope.statusText,
|
|
@@ -10995,7 +11441,12 @@ async function sendForScript(ports, request, signal, route = ports.transport.rou
|
|
|
10995
11441
|
async function applyChanges(ports, spec, changes, notes, secrets) {
|
|
10996
11442
|
const active = ports.environment();
|
|
10997
11443
|
const replaced = /* @__PURE__ */ new Map();
|
|
10998
|
-
const kept = async (api, rows2, scope) => (await intoVault(ports, api, rows2, scope, notes, replaced)).map(({ key, value, json }) => ({
|
|
11444
|
+
const kept = async (api, rows2, scope) => (await intoVault(ports, api, rows2, scope, notes, replaced)).map(({ key, value, json }) => ({
|
|
11445
|
+
key,
|
|
11446
|
+
value: value === null || !secrets ? value : secrets.text(value),
|
|
11447
|
+
keepSecret: true,
|
|
11448
|
+
...json && { json }
|
|
11449
|
+
}));
|
|
10999
11450
|
const environment2 = await kept("pm.environment", active?.vars ?? [], changes.environment);
|
|
11000
11451
|
const global = await kept("pm.globals", ports.globals(), changes.global);
|
|
11001
11452
|
const collection = await kept("pm.collectionVariables", spec.collection?.variables() ?? [], changes.collection);
|
|
@@ -11006,7 +11457,10 @@ async function applyChanges(ports, spec, changes, notes, secrets) {
|
|
|
11006
11457
|
if (global.length) await ports.changeGlobals(global);
|
|
11007
11458
|
if (collection.length) {
|
|
11008
11459
|
if (spec.collection) await spec.collection.change(collection);
|
|
11009
|
-
else
|
|
11460
|
+
else
|
|
11461
|
+
notes.push(
|
|
11462
|
+
"[warn] pm.collectionVariables: this request is in no collection, so what the script set was not kept."
|
|
11463
|
+
);
|
|
11010
11464
|
}
|
|
11011
11465
|
return async () => {
|
|
11012
11466
|
for (const [id, plaintext] of replaced) await ports.vault.replaceSecret(id, plaintext);
|
|
@@ -11026,7 +11480,8 @@ async function intoVault(ports, api, rows2, changes, notes, replaced) {
|
|
|
11026
11480
|
continue;
|
|
11027
11481
|
}
|
|
11028
11482
|
const old = await ports.vault.readSecret(id);
|
|
11029
|
-
if (old === null || !await ports.vault.replaceSecret(id, change.value))
|
|
11483
|
+
if (old === null || !await ports.vault.replaceSecret(id, change.value))
|
|
11484
|
+
refused("the secret this variable refers to is not in the vault");
|
|
11030
11485
|
else if (!replaced.has(id)) replaced.set(id, old);
|
|
11031
11486
|
}
|
|
11032
11487
|
}
|
|
@@ -11069,7 +11524,9 @@ function stringifyPayload(payload) {
|
|
|
11069
11524
|
// ../core/src/send/build.ts
|
|
11070
11525
|
var UnresolvedVariablesError = class extends SendBlockedError {
|
|
11071
11526
|
constructor(names) {
|
|
11072
|
-
super(
|
|
11527
|
+
super(
|
|
11528
|
+
`${names.map((name) => `{{${name}}}`).join(", ")} ${names.length === 1 ? "has" : "have"} no value. The request was not sent.`
|
|
11529
|
+
);
|
|
11073
11530
|
this.names = names;
|
|
11074
11531
|
}
|
|
11075
11532
|
names;
|
|
@@ -11151,7 +11608,8 @@ function buildRequest(content, context) {
|
|
|
11151
11608
|
const to = hostOf(endpointText, scopes());
|
|
11152
11609
|
if (from === void 0 || from === to || composed?.accepted?.has(to)) return withSecrets();
|
|
11153
11610
|
return (context.askHost?.(from, to) ?? Promise.resolve(false)).then((accepted) => {
|
|
11154
|
-
if (!accepted)
|
|
11611
|
+
if (!accepted)
|
|
11612
|
+
throw new SendDeclinedError("The request was not sent, and what the pre-request script set was undone.");
|
|
11155
11613
|
composed?.accepted?.add(to);
|
|
11156
11614
|
return withSecrets();
|
|
11157
11615
|
});
|
|
@@ -11161,11 +11619,17 @@ function build(content, resolver, context) {
|
|
|
11161
11619
|
const auth3 = resolveAuth(sentAuth(content.auth, context.ancestors), resolve);
|
|
11162
11620
|
const withoutValue = [...resolver.unresolved];
|
|
11163
11621
|
const sent = isBodyMethod(content.method) ? resolveBody(content.body, resolve, context) : void 0;
|
|
11164
|
-
const finish = (body3, token2) => sign(
|
|
11622
|
+
const finish = (body3, token2) => sign(
|
|
11623
|
+
assemble(content, body3, resolver, { ancestors: context.ancestors, token: token2, lastEventId: context.lastEventId }),
|
|
11624
|
+
auth3
|
|
11625
|
+
);
|
|
11165
11626
|
const oauth = auth3.type === "oauth2" ? oauthOf(content.auth, context.ancestors, context.scopes()) : null;
|
|
11166
|
-
if (auth3.type !== "oauth2" || !oauth)
|
|
11627
|
+
if (auth3.type !== "oauth2" || !oauth)
|
|
11628
|
+
return sent instanceof Promise ? sent.then((body3) => finish(body3)) : finish(sent);
|
|
11167
11629
|
if (withoutValue.length) {
|
|
11168
|
-
throw new SendBlockedError(
|
|
11630
|
+
throw new SendBlockedError(
|
|
11631
|
+
`The OAuth 2.0 auth uses ${withoutValue.map((name) => `{{${name}}}`).join(", ")}, which ${withoutValue.length === 1 ? "has" : "have"} no value. No token was asked for, and nothing was sent.`
|
|
11632
|
+
);
|
|
11169
11633
|
}
|
|
11170
11634
|
if (!context.oauthToken) throw new SendBlockedError("OAuth 2.0 tokens are not fetched here. Nothing was sent.");
|
|
11171
11635
|
const token = context.oauthToken(auth3, oauth.key, context.renewToken ?? false).catch((error) => {
|
|
@@ -11176,18 +11640,30 @@ function build(content, resolver, context) {
|
|
|
11176
11640
|
function sign(built2, auth3) {
|
|
11177
11641
|
if (auth3.type !== "awsv4" || !URL.canParse(built2.url)) return built2;
|
|
11178
11642
|
if (built2.body instanceof FormData) {
|
|
11179
|
-
throw new SendBlockedError(
|
|
11643
|
+
throw new SendBlockedError(
|
|
11644
|
+
"AWS Signature cannot sign a multipart form: the browser writes the form's boundary as it sends, so the app does not have the bytes to sign. Send a file as a File body, or the fields as a URL-encoded form. Nothing was sent."
|
|
11645
|
+
);
|
|
11180
11646
|
}
|
|
11181
|
-
if (!globalThis.crypto?.subtle)
|
|
11647
|
+
if (!globalThis.crypto?.subtle)
|
|
11648
|
+
throw new SendBlockedError(
|
|
11649
|
+
"AWS Signature needs the browser's cryptography, which a page has only over https or from localhost. Nothing was sent."
|
|
11650
|
+
);
|
|
11182
11651
|
const add = async (body3) => {
|
|
11183
11652
|
const own3 = built2.headers.filter(([name]) => /^(content-type$|x-amz-)/i.test(name));
|
|
11184
|
-
const { headers: signature } = await signV4(
|
|
11653
|
+
const { headers: signature } = await signV4(
|
|
11654
|
+
{ method: built2.method, url: built2.url, headers: own3, body: body3 },
|
|
11655
|
+
auth3,
|
|
11656
|
+
/* @__PURE__ */ new Date()
|
|
11657
|
+
);
|
|
11185
11658
|
const replaced = new Set(signature.map(([name]) => name.toLowerCase()));
|
|
11186
11659
|
return {
|
|
11187
11660
|
...built2,
|
|
11188
11661
|
headers: [...built2.headers.filter(([name]) => !replaced.has(name.toLowerCase())), ...signature],
|
|
11189
11662
|
// The signature's header too: a server may send it back.
|
|
11190
|
-
credentials: [
|
|
11663
|
+
credentials: [
|
|
11664
|
+
...built2.credentials,
|
|
11665
|
+
...signature.filter(([name]) => isCredentialHeader(name)).map(([, value]) => value)
|
|
11666
|
+
]
|
|
11191
11667
|
};
|
|
11192
11668
|
};
|
|
11193
11669
|
if (built2.body instanceof Blob) return built2.body.arrayBuffer().then((bytes) => add(new Uint8Array(bytes)));
|
|
@@ -11219,8 +11695,16 @@ function assemble(content, sent, resolver, extra) {
|
|
|
11219
11695
|
body: sent?.data,
|
|
11220
11696
|
// An OAuth 2.0 token is kept like a vault secret and masked like one: in history, in every export, and in what a script is shown.
|
|
11221
11697
|
secrets: [...resolver.taint, ...extra.token ? [extra.token] : []],
|
|
11698
|
+
// By name only, for the exchange record (D4.12): the variables that brought a vault secret in, and the token.
|
|
11699
|
+
vaulted: [
|
|
11700
|
+
...[...resolver.vaulted].map(([name, scope]) => ({ name, ...scope && { scope } })),
|
|
11701
|
+
...extra.token ? [{ name: "OAuth 2.0 token" }] : []
|
|
11702
|
+
],
|
|
11222
11703
|
// Also what a credential header carries, typed by hand or built from the Auth tab: a server may send it back.
|
|
11223
|
-
credentials: [
|
|
11704
|
+
credentials: [
|
|
11705
|
+
...credentialsOf(auth3),
|
|
11706
|
+
...headers2.filter(([name]) => isCredentialHeader(name)).map(([, value]) => value)
|
|
11707
|
+
].filter((value) => value)
|
|
11224
11708
|
};
|
|
11225
11709
|
}
|
|
11226
11710
|
function resolveBody(body3, resolve, context) {
|
|
@@ -11241,7 +11725,11 @@ function resolveBody(body3, resolve, context) {
|
|
|
11241
11725
|
return { data: new URLSearchParams(pairs2).toString(), contentType: "application/x-www-form-urlencoded" };
|
|
11242
11726
|
}
|
|
11243
11727
|
case "multipart":
|
|
11244
|
-
return multipartForm(
|
|
11728
|
+
return multipartForm(
|
|
11729
|
+
(body3.multipart ?? []).filter((item) => item.enabled),
|
|
11730
|
+
resolve,
|
|
11731
|
+
context
|
|
11732
|
+
);
|
|
11245
11733
|
case "binary":
|
|
11246
11734
|
return binaryFile(body3.binary, context);
|
|
11247
11735
|
}
|
|
@@ -11284,10 +11772,19 @@ async function runOutcome(send, declined) {
|
|
|
11284
11772
|
exchange: result.history
|
|
11285
11773
|
};
|
|
11286
11774
|
} catch (error) {
|
|
11287
|
-
if (error instanceof RequestSkipped)
|
|
11775
|
+
if (error instanceof RequestSkipped)
|
|
11776
|
+
return {
|
|
11777
|
+
outcome: {
|
|
11778
|
+
skipped: true,
|
|
11779
|
+
tests: tests(error.testResults),
|
|
11780
|
+
...error.nextRequest !== void 0 && { nextRequest: error.nextRequest }
|
|
11781
|
+
}
|
|
11782
|
+
};
|
|
11288
11783
|
if (error instanceof SendDeclinedError) return { outcome: { error: error.message, tests: [], end: declined } };
|
|
11289
|
-
if (error instanceof PreRequestScriptError)
|
|
11290
|
-
|
|
11784
|
+
if (error instanceof PreRequestScriptError)
|
|
11785
|
+
return { outcome: { error: error.message, tests: tests(error.testResults) } };
|
|
11786
|
+
if (error instanceof SendBlockedError || error instanceof VariableNestingError)
|
|
11787
|
+
return { outcome: { error: error.message, tests: [] } };
|
|
11291
11788
|
throw error;
|
|
11292
11789
|
}
|
|
11293
11790
|
}
|
|
@@ -11314,7 +11811,14 @@ function validateWorkspaceFile(value) {
|
|
|
11314
11811
|
}
|
|
11315
11812
|
return records;
|
|
11316
11813
|
};
|
|
11317
|
-
const [collections, folders, requests, environments, secrets, meta] = [
|
|
11814
|
+
const [collections, folders, requests, environments, secrets, meta] = [
|
|
11815
|
+
"collections",
|
|
11816
|
+
"folders",
|
|
11817
|
+
"requests",
|
|
11818
|
+
"environments",
|
|
11819
|
+
"secrets",
|
|
11820
|
+
"meta"
|
|
11821
|
+
].map(list4);
|
|
11318
11822
|
if (errors.length) return errors;
|
|
11319
11823
|
const idOf2 = (doc) => isObject2(doc["meta"]) ? doc["meta"]["id"] : void 0;
|
|
11320
11824
|
const known = new Set(collections.map(idOf2));
|
|
@@ -11327,12 +11831,21 @@ function validateWorkspaceFile(value) {
|
|
|
11327
11831
|
folders: folders.filter((folder) => folder["collectionId"] === id),
|
|
11328
11832
|
requests: requests.filter((request) => request["collectionId"] === id)
|
|
11329
11833
|
});
|
|
11330
|
-
errors.push(
|
|
11331
|
-
|
|
11834
|
+
errors.push(
|
|
11835
|
+
...(result.errors ?? []).map(
|
|
11836
|
+
(issue) => `collection "${String(collection["name"])}": ${issue.path}: ${issue.message}`
|
|
11837
|
+
)
|
|
11838
|
+
);
|
|
11839
|
+
if (!isObject2(collection["scriptTrust"]))
|
|
11840
|
+
errors.push(`collection "${String(collection["name"])}": scriptTrust is missing.`);
|
|
11332
11841
|
}
|
|
11333
|
-
for (const [kind, docs] of [
|
|
11842
|
+
for (const [kind, docs] of [
|
|
11843
|
+
["folders", folders],
|
|
11844
|
+
["requests", requests]
|
|
11845
|
+
]) {
|
|
11334
11846
|
docs.forEach((doc, index) => {
|
|
11335
|
-
if (!known.has(doc["collectionId"]))
|
|
11847
|
+
if (!known.has(doc["collectionId"]))
|
|
11848
|
+
errors.push(`stores.${kind}[${index}]: belongs to a collection that is not in the file.`);
|
|
11336
11849
|
});
|
|
11337
11850
|
}
|
|
11338
11851
|
errors.push(...validateEnvironmentExport({ $id: ENVIRONMENTS_FORMAT, environments }).errors ?? []);
|
|
@@ -11341,9 +11854,14 @@ function validateWorkspaceFile(value) {
|
|
|
11341
11854
|
const key = record["key"];
|
|
11342
11855
|
if (key === META_STATE_KEY) {
|
|
11343
11856
|
const active = record["activeEnvironmentId"];
|
|
11344
|
-
if (active !== null && active !== void 0 && typeof active !== "string")
|
|
11857
|
+
if (active !== null && active !== void 0 && typeof active !== "string")
|
|
11858
|
+
errors.push("stores.meta: the active environment must be an id or null.");
|
|
11345
11859
|
} else if (key === META_GLOBALS_KEY) {
|
|
11346
|
-
errors.push(
|
|
11860
|
+
errors.push(
|
|
11861
|
+
...validateRows(record["variables"], "stores.meta.globals.variables").map(
|
|
11862
|
+
(issue) => `${issue.path}: ${issue.message}`
|
|
11863
|
+
)
|
|
11864
|
+
);
|
|
11347
11865
|
} else if (key === META_VAULT_KEY) {
|
|
11348
11866
|
vault = record;
|
|
11349
11867
|
} else {
|
|
@@ -11354,7 +11872,8 @@ function validateWorkspaceFile(value) {
|
|
|
11354
11872
|
const issues = validateVaultFile({ $id: VAULT_FILE_FORMAT, vault, secrets });
|
|
11355
11873
|
errors.push(...issues.map((issue) => `vault: ${issue.path}: ${issue.message}`));
|
|
11356
11874
|
secrets.forEach((secret, index) => {
|
|
11357
|
-
if (!isObject2(secret["meta"]) || secret["meta"]["id"] !== secret["id"])
|
|
11875
|
+
if (!isObject2(secret["meta"]) || secret["meta"]["id"] !== secret["id"])
|
|
11876
|
+
errors.push(`stores.secrets[${index}]: meta.id must be the secret's id.`);
|
|
11358
11877
|
});
|
|
11359
11878
|
}
|
|
11360
11879
|
const history = own(stores, "history");
|
|
@@ -11363,16 +11882,47 @@ function validateWorkspaceFile(value) {
|
|
|
11363
11882
|
const path = `stores.history[${index}]`;
|
|
11364
11883
|
const sent = entry["sent"];
|
|
11365
11884
|
if (typeof entry["createdAt"] !== "number") errors.push(`${path}.createdAt: Value must be a number.`);
|
|
11366
|
-
if (entry["route"] !== "direct" && entry["route"] !== "bridge")
|
|
11885
|
+
if (entry["route"] !== "direct" && entry["route"] !== "bridge")
|
|
11886
|
+
errors.push(`${path}.route: Value must be direct or bridge.`);
|
|
11367
11887
|
if (!isObject2(sent) || typeof sent["method"] !== "string" || typeof sent["url"] !== "string" || !Array.isArray(sent["headers"])) {
|
|
11368
11888
|
errors.push(`${path}.sent: Value must hold a method, a URL and headers.`);
|
|
11369
11889
|
}
|
|
11370
|
-
errors.push(
|
|
11890
|
+
errors.push(
|
|
11891
|
+
...validateRequestContent(entry["template"], `${path}.template`).map(
|
|
11892
|
+
(issue) => `${issue.path}: ${issue.message}`
|
|
11893
|
+
)
|
|
11894
|
+
);
|
|
11895
|
+
if (Object.hasOwn(entry, "record")) errors.push(...recordProblems(entry["record"], `${path}.record`));
|
|
11896
|
+
if (Object.hasOwn(entry, "model")) {
|
|
11897
|
+
const model = entry["model"];
|
|
11898
|
+
if (!isObject2(model) || !["tests", "draft", "explain", "test"].includes(String(model["action"])) || typeof model["model"] !== "string" || Object.keys(model).some((key) => key !== "action" && key !== "model"))
|
|
11899
|
+
errors.push(`${path}.model: Value must be the action and the model's name.`);
|
|
11900
|
+
}
|
|
11901
|
+
if (Object.hasOwn(entry, "sentFrom")) {
|
|
11902
|
+
const from = entry["sentFrom"];
|
|
11903
|
+
if (!isObject2(from) || typeof from["name"] !== "string" || !Array.isArray(from["path"]) || !from["path"].every((name) => typeof name === "string") || Object.keys(from).some((key) => key !== "name" && key !== "path"))
|
|
11904
|
+
errors.push(
|
|
11905
|
+
`${path}.sentFrom: Value must be the request's name and the names of its collection and folders.`
|
|
11906
|
+
);
|
|
11907
|
+
}
|
|
11371
11908
|
});
|
|
11372
11909
|
}
|
|
11373
11910
|
return errors;
|
|
11374
11911
|
}
|
|
11375
11912
|
|
|
11913
|
+
// ../core/src/import/import-error.ts
|
|
11914
|
+
var ImportError = class extends Error {
|
|
11915
|
+
constructor(message, issues = [], cause) {
|
|
11916
|
+
super(message, { cause });
|
|
11917
|
+
this.issues = issues;
|
|
11918
|
+
}
|
|
11919
|
+
issues;
|
|
11920
|
+
name = "ImportError";
|
|
11921
|
+
};
|
|
11922
|
+
|
|
11923
|
+
// ../core/src/share/link.ts
|
|
11924
|
+
var SHARE_LINK_MAX_BYTES = 12 * 1024;
|
|
11925
|
+
|
|
11376
11926
|
// ../core/src/run/data-file.ts
|
|
11377
11927
|
var DataFileError = class extends Error {
|
|
11378
11928
|
name = "DataFileError";
|
|
@@ -11414,28 +11964,37 @@ function parseDataFile(text3, fileName) {
|
|
|
11414
11964
|
if (/\.json$/i.test(fileName)) {
|
|
11415
11965
|
const parsed2 = parseJson(text3);
|
|
11416
11966
|
const isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11417
|
-
if (!parsed2.ok || !Array.isArray(parsed2.value) || !parsed2.value.every(isObject5))
|
|
11967
|
+
if (!parsed2.ok || !Array.isArray(parsed2.value) || !parsed2.value.every(isObject5))
|
|
11968
|
+
throw new DataFileError("A JSON data file is a list of objects, one for each iteration.");
|
|
11418
11969
|
return parsed2.value.map(
|
|
11419
|
-
(entry) => Object.entries(entry).map(
|
|
11970
|
+
(entry) => Object.entries(entry).map(
|
|
11971
|
+
([key, value]) => typeof value === "string" ? { key, value, enabled: true } : { key, value: stringifyJson(value) ?? "null", enabled: true, json: true }
|
|
11972
|
+
)
|
|
11420
11973
|
);
|
|
11421
11974
|
}
|
|
11422
11975
|
const [names, ...records] = parseCsv(text3);
|
|
11423
|
-
if (!names?.some((name) => name))
|
|
11424
|
-
|
|
11976
|
+
if (!names?.some((name) => name))
|
|
11977
|
+
throw new DataFileError("A CSV data file has the names of its values in its first line.");
|
|
11978
|
+
return records.filter((record) => record.length > 1 || record[0] !== "").map(
|
|
11979
|
+
(record) => names.flatMap((key, index) => key ? [{ key, value: record[index] ?? "", enabled: true }] : [])
|
|
11980
|
+
);
|
|
11425
11981
|
}
|
|
11426
11982
|
|
|
11427
|
-
// ../core/src/import/import-error.ts
|
|
11428
|
-
var ImportError = class extends Error {
|
|
11429
|
-
constructor(message, issues = []) {
|
|
11430
|
-
super(message);
|
|
11431
|
-
this.issues = issues;
|
|
11432
|
-
}
|
|
11433
|
-
issues;
|
|
11434
|
-
name = "ImportError";
|
|
11435
|
-
};
|
|
11436
|
-
|
|
11437
11983
|
// ../core/src/import/curl.ts
|
|
11438
|
-
var ANSI_C = {
|
|
11984
|
+
var ANSI_C = {
|
|
11985
|
+
n: "\n",
|
|
11986
|
+
t: " ",
|
|
11987
|
+
r: "\r",
|
|
11988
|
+
a: "\x07",
|
|
11989
|
+
b: "\b",
|
|
11990
|
+
e: "\x1B",
|
|
11991
|
+
f: "\f",
|
|
11992
|
+
v: "\v",
|
|
11993
|
+
"\\": "\\",
|
|
11994
|
+
"'": "'",
|
|
11995
|
+
'"': '"',
|
|
11996
|
+
"?": "?"
|
|
11997
|
+
};
|
|
11439
11998
|
function ansiEscape(text3, at) {
|
|
11440
11999
|
const numbered = [
|
|
11441
12000
|
[/^x([0-9a-fA-F]{1,2})/, 16],
|
|
@@ -11568,7 +12127,33 @@ var FLAGS = {
|
|
|
11568
12127
|
"--insecure": "insecure",
|
|
11569
12128
|
"--compressed": "compressed"
|
|
11570
12129
|
};
|
|
11571
|
-
var QUIET_FLAGS = /* @__PURE__ */ new Set([
|
|
12130
|
+
var QUIET_FLAGS = /* @__PURE__ */ new Set([
|
|
12131
|
+
"-s",
|
|
12132
|
+
"--silent",
|
|
12133
|
+
"-S",
|
|
12134
|
+
"--show-error",
|
|
12135
|
+
"-v",
|
|
12136
|
+
"--verbose",
|
|
12137
|
+
"-i",
|
|
12138
|
+
"--include",
|
|
12139
|
+
"-L",
|
|
12140
|
+
"--location",
|
|
12141
|
+
"-f",
|
|
12142
|
+
"--fail",
|
|
12143
|
+
"-#",
|
|
12144
|
+
"--progress-bar",
|
|
12145
|
+
"-g",
|
|
12146
|
+
"--globoff",
|
|
12147
|
+
"-N",
|
|
12148
|
+
"--no-buffer",
|
|
12149
|
+
"--http1.1",
|
|
12150
|
+
"--http2",
|
|
12151
|
+
"-4",
|
|
12152
|
+
"-6",
|
|
12153
|
+
"-q",
|
|
12154
|
+
"-n",
|
|
12155
|
+
"-j"
|
|
12156
|
+
]);
|
|
11572
12157
|
var SKIPPED_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
11573
12158
|
"-o",
|
|
11574
12159
|
"--output",
|
|
@@ -11650,7 +12235,9 @@ function formPart(value, warnings) {
|
|
|
11650
12235
|
if (quotedFile) return { name, fileName: quotedFile[1].replace(/\\(.)/g, "$1") };
|
|
11651
12236
|
if (content.startsWith("@")) return { name, fileName: content.slice(1).split(/[;,]/)[0] };
|
|
11652
12237
|
if (content.startsWith("<")) {
|
|
11653
|
-
warnings.push(
|
|
12238
|
+
warnings.push(
|
|
12239
|
+
`The form part ${name} takes its text from the file ${content.slice(1).split(";")[0]}, which was not read: the part is empty.`
|
|
12240
|
+
);
|
|
11654
12241
|
return { name, value: "" };
|
|
11655
12242
|
}
|
|
11656
12243
|
return { name, value: content.replace(/;type=[^;]*$/, "") };
|
|
@@ -11733,7 +12320,8 @@ function parseCurl(text3) {
|
|
|
11733
12320
|
} else if (word.startsWith("--")) {
|
|
11734
12321
|
if (Object.hasOwn(VALUE_OPTIONS, word)) take(VALUE_OPTIONS[word], valueOf(word));
|
|
11735
12322
|
else if (Object.hasOwn(FLAGS, word)) ({ get, head } = flag(FLAGS[word], { get, head }, warnings));
|
|
11736
|
-
else if (SKIPPED_VALUE_OPTIONS.has(word))
|
|
12323
|
+
else if (SKIPPED_VALUE_OPTIONS.has(word))
|
|
12324
|
+
warnings.push(`${word} ${valueOf(word)} was left out: the app has nothing it applies to.`);
|
|
11737
12325
|
else if (!QUIET_FLAGS.has(word)) warnings.push(`${word} was left out: the app does not know this option.`);
|
|
11738
12326
|
} else {
|
|
11739
12327
|
for (let at = 1; at < word.length; at++) {
|
|
@@ -11753,14 +12341,18 @@ function parseCurl(text3) {
|
|
|
11753
12341
|
}
|
|
11754
12342
|
}
|
|
11755
12343
|
if (!urls.length) throw new ImportError("The cURL command has no address.");
|
|
11756
|
-
if (urls.length > 1)
|
|
12344
|
+
if (urls.length > 1)
|
|
12345
|
+
warnings.push(
|
|
12346
|
+
`The command names ${urls.length} addresses. The first is used; the others were left out: ${urls.slice(1).join(", ")}.`
|
|
12347
|
+
);
|
|
11757
12348
|
let url = urls[0];
|
|
11758
12349
|
const type = (contentType(headers2) ?? "").toLowerCase();
|
|
11759
12350
|
let body3 = { mode: "none" };
|
|
11760
12351
|
const files = data.flatMap((datum) => "file" in datum ? [datum.file] : []);
|
|
11761
12352
|
if (parts.length) {
|
|
11762
12353
|
body3 = { mode: "multipart", parts };
|
|
11763
|
-
if (data.length)
|
|
12354
|
+
if (data.length)
|
|
12355
|
+
warnings.push("The command has both a form (-F) and data (-d). curl refuses that; the form is kept.");
|
|
11764
12356
|
} else if (get) {
|
|
11765
12357
|
const query = data.flatMap((datum) => "text" in datum ? [datum.text] : []).join("&");
|
|
11766
12358
|
if (files.length) warnings.push(`The data of the file ${files.join(", ")} was left out: the file was not read.`);
|
|
@@ -11773,14 +12365,21 @@ function parseCurl(text3) {
|
|
|
11773
12365
|
body3 = !type || type.startsWith("application/x-www-form-urlencoded") ? { mode: "urlencoded", fields: [...new URLSearchParams(wire)] } : { mode: "raw", text: wire };
|
|
11774
12366
|
}
|
|
11775
12367
|
method ??= head ? "HEAD" : body3.mode === "none" ? "GET" : "POST";
|
|
11776
|
-
if (!isHttpMethod(method))
|
|
12368
|
+
if (!isHttpMethod(method))
|
|
12369
|
+
throw new ImportError(
|
|
12370
|
+
`The cURL command's method, ${method}, is not an HTTP method: one word of at most 32 characters.`
|
|
12371
|
+
);
|
|
11777
12372
|
return { request: { method, url, headers: headers2, body: body3 }, ...basic && { basic }, warnings };
|
|
11778
12373
|
}
|
|
11779
12374
|
function flag(kind, state, warnings) {
|
|
11780
12375
|
if (kind === "get") return { ...state, get: true };
|
|
11781
12376
|
if (kind === "head") return { ...state, head: true };
|
|
11782
|
-
if (kind === "insecure")
|
|
11783
|
-
|
|
12377
|
+
if (kind === "insecure")
|
|
12378
|
+
warnings.push(
|
|
12379
|
+
"-k (do not check the server's certificate) was left out: a browser always checks it, and the Local Bridge cannot skip the check yet."
|
|
12380
|
+
);
|
|
12381
|
+
if (kind === "compressed")
|
|
12382
|
+
warnings.push("--compressed was left out: the browser asks for a compressed answer by itself.");
|
|
11784
12383
|
return state;
|
|
11785
12384
|
}
|
|
11786
12385
|
function languageOf(type) {
|
|
@@ -11799,8 +12398,15 @@ function curlRequest(parsed2) {
|
|
|
11799
12398
|
const language = languageOf(type.toLowerCase());
|
|
11800
12399
|
const row = (key, value) => ({ key, value, enabled: true });
|
|
11801
12400
|
const implied = body3.mode === "urlencoded" ? "application/x-www-form-urlencoded" : body3.mode === "multipart" ? "multipart/form-data" : body3.mode === "raw" ? RAW_CONTENT_TYPES[language] : null;
|
|
11802
|
-
const headers2 = request.headers.filter(
|
|
11803
|
-
|
|
12401
|
+
const headers2 = request.headers.filter(
|
|
12402
|
+
([name, value]) => !(name.toLowerCase() === "content-type" && implied !== null && value.toLowerCase().split(";")[0].trim() === implied)
|
|
12403
|
+
);
|
|
12404
|
+
const content = {
|
|
12405
|
+
...emptyRequest(),
|
|
12406
|
+
method: request.method,
|
|
12407
|
+
url: request.url,
|
|
12408
|
+
headers: headers2.map(([name, value]) => row(name, value))
|
|
12409
|
+
};
|
|
11804
12410
|
if (basic) content.auth = { type: "basic", ...basic };
|
|
11805
12411
|
if (body3.mode === "raw") {
|
|
11806
12412
|
content.body = { mode: "raw", raw: { language, text: body3.text } };
|
|
@@ -11809,12 +12415,23 @@ function curlRequest(parsed2) {
|
|
|
11809
12415
|
} else if (body3.mode === "multipart") {
|
|
11810
12416
|
content.body = {
|
|
11811
12417
|
mode: "multipart",
|
|
11812
|
-
multipart: body3.parts.map(
|
|
12418
|
+
multipart: body3.parts.map(
|
|
12419
|
+
(part) => "value" in part ? { kind: "text", key: part.name, value: part.value, enabled: true } : { kind: "file", key: part.name, fileId: "", fileName: part.fileName, enabled: true }
|
|
12420
|
+
)
|
|
11813
12421
|
};
|
|
11814
|
-
for (const part of body3.parts)
|
|
12422
|
+
for (const part of body3.parts)
|
|
12423
|
+
if ("fileName" in part)
|
|
12424
|
+
warnings.push(
|
|
12425
|
+
`The form part ${part.name} sends the file ${part.fileName}. Choose the file again in the Body tab: a command names a file, it does not hold it.`
|
|
12426
|
+
);
|
|
11815
12427
|
} else if (body3.mode === "binary") {
|
|
11816
|
-
content.body = {
|
|
11817
|
-
|
|
12428
|
+
content.body = {
|
|
12429
|
+
mode: "binary",
|
|
12430
|
+
...type && { binary: { fileId: "", fileName: body3.fileName, contentType: type } }
|
|
12431
|
+
};
|
|
12432
|
+
warnings.push(
|
|
12433
|
+
`The body is the file ${body3.fileName}. Choose the file again in the Body tab: a command names a file, it does not hold it.`
|
|
12434
|
+
);
|
|
11818
12435
|
}
|
|
11819
12436
|
return { content, warnings };
|
|
11820
12437
|
}
|
|
@@ -11835,16 +12452,23 @@ function bodyOf(postData, warnings) {
|
|
|
11835
12452
|
const params = list(own2(postData, "params"));
|
|
11836
12453
|
if (type.startsWith("multipart/form-data")) {
|
|
11837
12454
|
if (!params.length) {
|
|
11838
|
-
warnings.push(
|
|
12455
|
+
warnings.push(
|
|
12456
|
+
"Its multipart body is in the file as text only, with a boundary of that one send. It is kept as text."
|
|
12457
|
+
);
|
|
11839
12458
|
return { mode: "raw", text: text(own2(postData, "text")) };
|
|
11840
12459
|
}
|
|
11841
12460
|
return {
|
|
11842
12461
|
mode: "multipart",
|
|
11843
|
-
parts: params.map(
|
|
12462
|
+
parts: params.map(
|
|
12463
|
+
(param) => typeof own2(param, "fileName") === "string" ? { name: text(own2(param, "name")), fileName: text(own2(param, "fileName")) } : { name: text(own2(param, "name")), value: text(own2(param, "value")) }
|
|
12464
|
+
)
|
|
11844
12465
|
};
|
|
11845
12466
|
}
|
|
11846
12467
|
if (type.startsWith("application/x-www-form-urlencoded")) {
|
|
11847
|
-
return {
|
|
12468
|
+
return {
|
|
12469
|
+
mode: "urlencoded",
|
|
12470
|
+
fields: params.length ? params.map((param) => [text(own2(param, "name")), text(own2(param, "value"))]) : [...new URLSearchParams(text(own2(postData, "text")))]
|
|
12471
|
+
};
|
|
11848
12472
|
}
|
|
11849
12473
|
const body3 = text(own2(postData, "text"));
|
|
11850
12474
|
if (own2(postData, "encoding") === "base64") {
|
|
@@ -11859,11 +12483,18 @@ function parseHar(value) {
|
|
|
11859
12483
|
return entries.flatMap((entry, index) => {
|
|
11860
12484
|
const request = own2(entry, "request");
|
|
11861
12485
|
const [method, url] = isObject3(request) ? [text(own2(request, "method")).toUpperCase(), text(own2(request, "url"))] : ["", ""];
|
|
11862
|
-
if (!isObject3(request) || !method || !url)
|
|
12486
|
+
if (!isObject3(request) || !method || !url)
|
|
12487
|
+
throw new ImportError(`The HAR's entry ${index + 1} has no request with a method and a URL.`);
|
|
11863
12488
|
const warnings = [];
|
|
11864
12489
|
const headers2 = list(own2(request, "headers")).map((header) => [text(own2(header, "name")), text(own2(header, "value"))]).filter(([name]) => name && !name.startsWith(":"));
|
|
11865
12490
|
const path = URL.canParse(url) ? new URL(url).pathname : url;
|
|
11866
|
-
return [
|
|
12491
|
+
return [
|
|
12492
|
+
{
|
|
12493
|
+
name: `${method} ${path}`,
|
|
12494
|
+
request: { method, url, headers: headers2, body: bodyOf(own2(request, "postData"), warnings) },
|
|
12495
|
+
warnings
|
|
12496
|
+
}
|
|
12497
|
+
];
|
|
11867
12498
|
});
|
|
11868
12499
|
}
|
|
11869
12500
|
|
|
@@ -11896,7 +12527,12 @@ function addressParts(raw) {
|
|
|
11896
12527
|
const host = colon === -1 ? authority : authority.slice(0, colon);
|
|
11897
12528
|
return {
|
|
11898
12529
|
...protocol !== void 0 && { protocol: back(protocol) },
|
|
11899
|
-
...user !== void 0 && {
|
|
12530
|
+
...user !== void 0 && {
|
|
12531
|
+
auth: {
|
|
12532
|
+
user: back(user.split(":")[0]),
|
|
12533
|
+
...user.includes(":") && { password: back(user.slice(user.indexOf(":") + 1)) }
|
|
12534
|
+
}
|
|
12535
|
+
},
|
|
11900
12536
|
host: host.startsWith("[") ? [back(host)] : host.split(".").map(back),
|
|
11901
12537
|
...port !== void 0 && { port: back(port) },
|
|
11902
12538
|
...path !== void 0 && { path: path.split("/").map(back) },
|
|
@@ -11931,7 +12567,11 @@ var description = (value) => {
|
|
|
11931
12567
|
const words = isObject4(value) ? text2(field3(value, "content")) : text2(value);
|
|
11932
12568
|
return words ? { description: words } : {};
|
|
11933
12569
|
};
|
|
11934
|
-
var rows = (value) => objects(value).map((entry) => ({
|
|
12570
|
+
var rows = (value) => objects(value).map((entry) => ({
|
|
12571
|
+
key: text2(field3(entry, "key") ?? field3(entry, "id")),
|
|
12572
|
+
value: text2(field3(entry, "value")),
|
|
12573
|
+
enabled: field3(entry, "disabled") !== true
|
|
12574
|
+
}));
|
|
11935
12575
|
function headers(value) {
|
|
11936
12576
|
if (typeof value !== "string") return rows(value);
|
|
11937
12577
|
return value.split(/\r?\n/).filter((line2) => line2.trim()).map((line2) => {
|
|
@@ -11956,15 +12596,30 @@ function auth(value, item, warnings, top) {
|
|
|
11956
12596
|
case "apikey":
|
|
11957
12597
|
return { type, key: read("key"), value: read("value"), in: read("in") === "query" ? "query" : "header" };
|
|
11958
12598
|
case "awsv4":
|
|
11959
|
-
return {
|
|
12599
|
+
return {
|
|
12600
|
+
type,
|
|
12601
|
+
accessKey: read("accessKey"),
|
|
12602
|
+
secretKey: read("secretKey"),
|
|
12603
|
+
sessionToken: read("sessionToken"),
|
|
12604
|
+
region: read("region"),
|
|
12605
|
+
service: read("service")
|
|
12606
|
+
};
|
|
11960
12607
|
case "oauth2": {
|
|
11961
12608
|
const grant = OAUTH_GRANTS_OF.get(read("grant_type"));
|
|
11962
12609
|
if (!grant) {
|
|
11963
|
-
warnings.push({
|
|
12610
|
+
warnings.push({
|
|
12611
|
+
item,
|
|
12612
|
+
message: `Its auth is OAuth 2.0 with a grant the app does not have (${read("grant_type").slice(0, 40)}) and is left out: it is sent with no auth.`
|
|
12613
|
+
});
|
|
11964
12614
|
return { type: "none" };
|
|
11965
12615
|
}
|
|
11966
|
-
if (read("accessToken"))
|
|
11967
|
-
|
|
12616
|
+
if (read("accessToken"))
|
|
12617
|
+
warnings.push({ item, message: "The OAuth 2.0 token in the file is not kept: get one in the Auth tab." });
|
|
12618
|
+
if (read("addTokenTo") === "queryParams")
|
|
12619
|
+
warnings.push({
|
|
12620
|
+
item,
|
|
12621
|
+
message: "Its OAuth 2.0 token was added to the address: the app sends it in the Authorization header."
|
|
12622
|
+
});
|
|
11968
12623
|
return {
|
|
11969
12624
|
type,
|
|
11970
12625
|
grant,
|
|
@@ -11980,7 +12635,10 @@ function auth(value, item, warnings, top) {
|
|
|
11980
12635
|
};
|
|
11981
12636
|
}
|
|
11982
12637
|
default:
|
|
11983
|
-
warnings.push({
|
|
12638
|
+
warnings.push({
|
|
12639
|
+
item,
|
|
12640
|
+
message: `Its auth is of a type the app does not have (${type || "no type"}) and is left out: it is sent with no auth.`
|
|
12641
|
+
});
|
|
11984
12642
|
return { type: "none" };
|
|
11985
12643
|
}
|
|
11986
12644
|
}
|
|
@@ -11998,21 +12656,30 @@ function scripts(value, item, warnings) {
|
|
|
11998
12656
|
return Array.isArray(exec) ? exec.map(text2).join("\n") : text2(exec);
|
|
11999
12657
|
}).join("\n");
|
|
12000
12658
|
const others = objects(value).map((event) => text2(field3(event, "listen"))).filter((listen) => listen !== "prerequest" && listen !== "test");
|
|
12001
|
-
if (others.length)
|
|
12659
|
+
if (others.length)
|
|
12660
|
+
warnings.push({
|
|
12661
|
+
item,
|
|
12662
|
+
message: `A script for "${others[0]}" is left out: the app runs a script before a request and after its response.`
|
|
12663
|
+
});
|
|
12002
12664
|
return { pre: of2("prerequest"), post: of2("test") };
|
|
12003
12665
|
}
|
|
12004
12666
|
function address(value, item, warnings) {
|
|
12005
12667
|
const inText = (url2) => (addressParts(url2).query ?? []).map((entry) => ({ key: entry.key, value: entry.value ?? "", enabled: true }));
|
|
12006
12668
|
if (!isObject4(value)) return { url: text2(value), params: inText(text2(value)) };
|
|
12007
|
-
const kept = (part, marks) => part.split(/(\{\{[^{}]*\}\})/).map(
|
|
12669
|
+
const kept = (part, marks) => part.split(/(\{\{[^{}]*\}\})/).map(
|
|
12670
|
+
(piece, index) => index % 2 ? piece : piece.replace(marks, (mark) => `%${mark.charCodeAt(0).toString(16).toUpperCase()}`)
|
|
12671
|
+
).join("");
|
|
12008
12672
|
const params = objects(field3(value, "query")).map((entry) => {
|
|
12009
12673
|
const enabled = field3(entry, "disabled") !== true;
|
|
12010
|
-
return {
|
|
12674
|
+
return {
|
|
12675
|
+
key: enabled ? kept(text2(field3(entry, "key")), /[#&=]/g) : text2(field3(entry, "key")),
|
|
12676
|
+
value: enabled ? kept(text2(field3(entry, "value")), /[#&]/g) : text2(field3(entry, "value")),
|
|
12677
|
+
enabled
|
|
12678
|
+
};
|
|
12011
12679
|
});
|
|
12012
|
-
const off = params.filter((row) => !row.enabled).map((row) => row.key);
|
|
12013
|
-
if (off.length) warnings.push({ item, message: `${off.length === 1 ? `A query parameter that is switched off (${off[0]}) is` : `${off.length} query parameters that are switched off are`} kept for an export, but the composer has no switch for a parameter: it does not show one, and saving the request there drops it.` });
|
|
12014
12680
|
const host = field3(value, "host");
|
|
12015
|
-
if (host === void 0)
|
|
12681
|
+
if (host === void 0)
|
|
12682
|
+
return { url: text2(field3(value, "raw")), params: params.length ? params : inText(text2(field3(value, "raw"))) };
|
|
12016
12683
|
const variables2 = objects(field3(value, "variable"));
|
|
12017
12684
|
const replaced = [];
|
|
12018
12685
|
const named2 = [];
|
|
@@ -12029,11 +12696,21 @@ function address(value, item, warnings) {
|
|
|
12029
12696
|
const protocol = field3(value, "protocol");
|
|
12030
12697
|
const port = field3(value, "port");
|
|
12031
12698
|
const path = field3(value, "path");
|
|
12032
|
-
const query = objects(field3(value, "query")).filter((entry) => field3(entry, "disabled") !== true).map(
|
|
12699
|
+
const query = objects(field3(value, "query")).filter((entry) => field3(entry, "disabled") !== true).map(
|
|
12700
|
+
(entry) => field3(entry, "value") === null || field3(entry, "value") === void 0 ? kept(text2(field3(entry, "key")), /[#&=]/g) : `${kept(text2(field3(entry, "key")), /[#&=]/g)}=${kept(text2(field3(entry, "value")), /[#&]/g)}`
|
|
12701
|
+
);
|
|
12033
12702
|
const hash = field3(value, "hash");
|
|
12034
12703
|
const url = (protocol === void 0 ? "" : `${text2(protocol)}://`) + (isObject4(login) ? `${text2(field3(login, "user"))}${field3(login, "password") === void 0 ? "" : `:${text2(field3(login, "password"))}`}@` : "") + joined(host, ".") + (port === void 0 ? "" : `:${text2(port)}`) + (path === void 0 ? "" : `/${Array.isArray(path) ? joined(path, "/", inPath) : text2(path).replace(/^\//, "").split("/").map(inPath).join("/")}`) + (query.length ? `?${query.join("&")}` : "") + (hash === void 0 ? "" : `#${text2(hash)}`);
|
|
12035
|
-
if (replaced.length)
|
|
12036
|
-
|
|
12704
|
+
if (replaced.length)
|
|
12705
|
+
warnings.push({
|
|
12706
|
+
item,
|
|
12707
|
+
message: `${replaced.length === 1 ? `The path variable ${replaced[0]} is` : `The path variables ${replaced.join(", ")} are`} replaced by ${replaced.length === 1 ? "its value" : "their values"} in the address: the app has no path variables.`
|
|
12708
|
+
});
|
|
12709
|
+
if (named2.length)
|
|
12710
|
+
warnings.push({
|
|
12711
|
+
item,
|
|
12712
|
+
message: `${named2.length === 1 ? `The path variable ${named2[0]} has` : `The path variables ${named2.join(", ")} have`} no value in the file: in the address ${named2.length === 1 ? "it is" : "they are"} ${named2.map((part) => `{{${part.slice(1)}}}`).join(", ")}, to be given a value as ${named2.length === 1 ? "a variable" : "variables"}.`
|
|
12713
|
+
});
|
|
12037
12714
|
return { url, params };
|
|
12038
12715
|
}
|
|
12039
12716
|
function body(value, item, warnings) {
|
|
@@ -12053,11 +12730,19 @@ function body(value, item, warnings) {
|
|
|
12053
12730
|
return field3(part, "type") === "file" ? { key, enabled, kind: "file", fileId: "", fileName: file(field3(part, "src")) } : { key, enabled, kind: "text", value: text2(field3(part, "value")) };
|
|
12054
12731
|
})
|
|
12055
12732
|
},
|
|
12056
|
-
...isObject4(field3(value, "file")) && {
|
|
12733
|
+
...isObject4(field3(value, "file")) && {
|
|
12734
|
+
binary: { fileId: "", fileName: file(field3(field3(value, "file"), "src")) }
|
|
12735
|
+
},
|
|
12057
12736
|
// Postman keeps the variables as text, as the app does.
|
|
12058
|
-
...isObject4(field3(value, "graphql")) && {
|
|
12737
|
+
...isObject4(field3(value, "graphql")) && {
|
|
12738
|
+
graphql: {
|
|
12739
|
+
query: text2(field3(field3(value, "graphql"), "query")),
|
|
12740
|
+
variables: text2(field3(field3(value, "graphql"), "variables"))
|
|
12741
|
+
}
|
|
12742
|
+
}
|
|
12059
12743
|
};
|
|
12060
|
-
if (kept.multipart?.some((part) => part.kind === "file") || kept.binary)
|
|
12744
|
+
if (kept.multipart?.some((part) => part.kind === "file") || kept.binary)
|
|
12745
|
+
warnings.push({ item, message: "A file of its body is named, not included: choose it again in the Body tab." });
|
|
12061
12746
|
if (field3(value, "disabled") === true) return { mode: "none", ...kept };
|
|
12062
12747
|
switch (field3(value, "mode")) {
|
|
12063
12748
|
case "raw":
|
|
@@ -12075,7 +12760,10 @@ function body(value, item, warnings) {
|
|
|
12075
12760
|
case "none":
|
|
12076
12761
|
return { mode: "none", ...kept };
|
|
12077
12762
|
default:
|
|
12078
|
-
warnings.push({
|
|
12763
|
+
warnings.push({
|
|
12764
|
+
item,
|
|
12765
|
+
message: `Its body is of a kind the app does not have (${text2(field3(value, "mode"))}) and is left out.`
|
|
12766
|
+
});
|
|
12079
12767
|
return { mode: "none", ...kept };
|
|
12080
12768
|
}
|
|
12081
12769
|
}
|
|
@@ -12120,7 +12808,10 @@ function postmanCollection(value) {
|
|
|
12120
12808
|
const method = text2(field3(source, "method") ?? "GET").toUpperCase();
|
|
12121
12809
|
examples += list2(field3(entry, "response")).length;
|
|
12122
12810
|
if (!url.trim() || !isHttpMethod(method)) {
|
|
12123
|
-
warnings.push({
|
|
12811
|
+
warnings.push({
|
|
12812
|
+
item: item2,
|
|
12813
|
+
message: !url.trim() ? "It has no address and is left out." : `Its method (${method.slice(0, 40)}) is not an HTTP method and it is left out.`
|
|
12814
|
+
});
|
|
12124
12815
|
continue;
|
|
12125
12816
|
}
|
|
12126
12817
|
requests.push({
|
|
@@ -12145,7 +12836,11 @@ function postmanCollection(value) {
|
|
|
12145
12836
|
};
|
|
12146
12837
|
walk2(field3(value, "item"), void 0, 0);
|
|
12147
12838
|
const item = `collection "${name}"`;
|
|
12148
|
-
if (examples)
|
|
12839
|
+
if (examples)
|
|
12840
|
+
warnings.push({
|
|
12841
|
+
item,
|
|
12842
|
+
message: `${examples} saved ${examples === 1 ? "response is" : "responses are"} left out: the app keeps responses in its history, not in a collection.`
|
|
12843
|
+
});
|
|
12149
12844
|
return {
|
|
12150
12845
|
payload: {
|
|
12151
12846
|
$id: COLLECTION_FORMAT,
|
|
@@ -12177,9 +12872,18 @@ function postmanEnvironment(value) {
|
|
|
12177
12872
|
meta: { id, createdAt: now, updatedAt: now },
|
|
12178
12873
|
name,
|
|
12179
12874
|
order: 0,
|
|
12180
|
-
vars: objects(isObject4(value) ? field3(value, "values") : value).map((entry) => ({
|
|
12875
|
+
vars: objects(isObject4(value) ? field3(value, "values") : value).map((entry) => ({
|
|
12876
|
+
key: text2(field3(entry, "key")),
|
|
12877
|
+
value: text2(field3(entry, "value")),
|
|
12878
|
+
enabled: field3(entry, "enabled") !== false && field3(entry, "disabled") !== true
|
|
12879
|
+
}))
|
|
12181
12880
|
},
|
|
12182
|
-
warnings: globals ? [
|
|
12881
|
+
warnings: globals ? [
|
|
12882
|
+
{
|
|
12883
|
+
item: `environment "${name}"`,
|
|
12884
|
+
message: "Postman's globals are imported as an environment: choose it as the active environment, or copy what you need into the app's globals."
|
|
12885
|
+
}
|
|
12886
|
+
] : []
|
|
12183
12887
|
};
|
|
12184
12888
|
}
|
|
12185
12889
|
|
|
@@ -12190,10 +12894,16 @@ function isInsomnia(value) {
|
|
|
12190
12894
|
return field3(value, "_type") === "export" || Object.hasOwn(value, "__export_format") || /^\w+\.insomnia(\.rest)?\/\d/.test(text2(field3(value, "type")));
|
|
12191
12895
|
}
|
|
12192
12896
|
var VARIABLE = /\{\{\s*(?:_\.|_(?=\[))?((?:\w[\w-]*|\[(?:'[^'\]]*'|"[^"\]]*")\])(?:\.\w[\w-]*|\[(?:'[^'\]]*'|"[^"\]]*")\])*)\s*\}\}/g;
|
|
12193
|
-
var variables = (value) => value.replace(
|
|
12897
|
+
var variables = (value) => value.replace(
|
|
12898
|
+
VARIABLE,
|
|
12899
|
+
(_match, path) => `{{${path.replace(/\[(?:'([^'\]]*)'|"([^"\]]*)")\]/g, (_part, single, double) => `.${single ?? double ?? ""}`).replace(/^\./, "")}}}`
|
|
12900
|
+
);
|
|
12194
12901
|
function foreign(value) {
|
|
12195
12902
|
const found = [];
|
|
12196
|
-
for (const [open, close] of [
|
|
12903
|
+
for (const [open, close] of [
|
|
12904
|
+
["{%", "%}"],
|
|
12905
|
+
["{{", "}}"]
|
|
12906
|
+
]) {
|
|
12197
12907
|
let at = value.indexOf(open);
|
|
12198
12908
|
while (at !== -1) {
|
|
12199
12909
|
const end = value.indexOf(close, at + 2);
|
|
@@ -12214,7 +12924,11 @@ function reader(item, warnings) {
|
|
|
12214
12924
|
return turned;
|
|
12215
12925
|
},
|
|
12216
12926
|
done: () => {
|
|
12217
|
-
if (templates.length)
|
|
12927
|
+
if (templates.length)
|
|
12928
|
+
warnings.push({
|
|
12929
|
+
item,
|
|
12930
|
+
message: `It uses a template of Insomnia's (${templates[0].slice(0, 40)}${templates.length > 1 ? `, and ${templates.length - 1} more` : ""}), which the app does not have: the text is kept as written.`
|
|
12931
|
+
});
|
|
12218
12932
|
}
|
|
12219
12933
|
};
|
|
12220
12934
|
}
|
|
@@ -12230,7 +12944,11 @@ function environment(name, vars) {
|
|
|
12230
12944
|
const now = Date.now();
|
|
12231
12945
|
return { id, meta: { id, createdAt: now, updatedAt: now }, name, order: 0, vars };
|
|
12232
12946
|
}
|
|
12233
|
-
var pairs = (value, said2) => objects(value).map((entry) => ({
|
|
12947
|
+
var pairs = (value, said2) => objects(value).map((entry) => ({
|
|
12948
|
+
key: said2(field3(entry, "name")),
|
|
12949
|
+
value: said2(field3(entry, "value")),
|
|
12950
|
+
...field3(entry, "disabled") === true && { disabled: true }
|
|
12951
|
+
})).filter((entry) => entry.key !== "" || entry.value !== "");
|
|
12234
12952
|
function auth2(value, said2) {
|
|
12235
12953
|
if (!isObject4(value) || typeof field3(value, "type") !== "string" || field3(value, "disabled") === true) return {};
|
|
12236
12954
|
const type = text2(field3(value, "type"));
|
|
@@ -12243,18 +12961,44 @@ function auth2(value, said2) {
|
|
|
12243
12961
|
case "bearer": {
|
|
12244
12962
|
const prefix = read("prefix").trim();
|
|
12245
12963
|
if (!prefix || prefix === "Bearer") return { auth: { type, bearer: { token: read("token") } } };
|
|
12246
|
-
return {
|
|
12964
|
+
return {
|
|
12965
|
+
auth: {
|
|
12966
|
+
type: "apikey",
|
|
12967
|
+
apikey: {
|
|
12968
|
+
key: "Authorization",
|
|
12969
|
+
value: prefix === "NO_PREFIX" ? read("token") : `${prefix} ${read("token")}`,
|
|
12970
|
+
in: "header"
|
|
12971
|
+
}
|
|
12972
|
+
}
|
|
12973
|
+
};
|
|
12247
12974
|
}
|
|
12248
12975
|
case "apikey": {
|
|
12249
12976
|
const place = text2(field3(value, "addTo"));
|
|
12250
12977
|
if (place && place !== "header" && place !== "queryParams") return { auth: { type: `an API key in a ${place}` } };
|
|
12251
|
-
return {
|
|
12978
|
+
return {
|
|
12979
|
+
auth: {
|
|
12980
|
+
type,
|
|
12981
|
+
apikey: { key: read("key"), value: read("value"), in: place === "queryParams" ? "query" : "header" }
|
|
12982
|
+
}
|
|
12983
|
+
};
|
|
12252
12984
|
}
|
|
12253
12985
|
case "iam":
|
|
12254
|
-
return {
|
|
12986
|
+
return {
|
|
12987
|
+
auth: {
|
|
12988
|
+
type: "awsv4",
|
|
12989
|
+
awsv4: {
|
|
12990
|
+
accessKey: read("accessKeyId"),
|
|
12991
|
+
secretKey: read("secretAccessKey"),
|
|
12992
|
+
sessionToken: read("sessionToken"),
|
|
12993
|
+
region: read("region"),
|
|
12994
|
+
service: read("service")
|
|
12995
|
+
}
|
|
12996
|
+
}
|
|
12997
|
+
};
|
|
12255
12998
|
case "oauth2": {
|
|
12256
12999
|
const grant = read("grantType");
|
|
12257
|
-
if (grant !== "authorization_code" && grant !== "client_credentials" && grant !== "password")
|
|
13000
|
+
if (grant !== "authorization_code" && grant !== "client_credentials" && grant !== "password")
|
|
13001
|
+
return { auth: { type, oauth2: { grant_type: grant || "none said" } } };
|
|
12258
13002
|
return {
|
|
12259
13003
|
auth: {
|
|
12260
13004
|
type,
|
|
@@ -12291,8 +13035,10 @@ function body2(value, said2) {
|
|
|
12291
13035
|
}
|
|
12292
13036
|
};
|
|
12293
13037
|
}
|
|
12294
|
-
if (type === "application/x-www-form-urlencoded" && Array.isArray(params))
|
|
12295
|
-
|
|
13038
|
+
if (type === "application/x-www-form-urlencoded" && Array.isArray(params))
|
|
13039
|
+
return { body: { mode: "urlencoded", urlencoded: pairs(params, said2) } };
|
|
13040
|
+
if (typeof field3(value, "fileName") === "string" && typeof field3(value, "text") !== "string")
|
|
13041
|
+
return { body: { mode: "file", file: { src: field3(value, "fileName") } } };
|
|
12296
13042
|
if (typeof field3(value, "text") !== "string" || !type && !field3(value, "text")) return {};
|
|
12297
13043
|
const language = /json|graphql/.test(type) ? "json" : type.includes("xml") ? "xml" : type.includes("html") ? "html" : type.includes("javascript") ? "javascript" : "text";
|
|
12298
13044
|
return { body: { mode: "raw", raw: said2(field3(value, "text")), options: { raw: { language } } } };
|
|
@@ -12301,7 +13047,11 @@ function builder(warnings) {
|
|
|
12301
13047
|
const folderVariables = [];
|
|
12302
13048
|
const scripts2 = (item, { pre, post }) => {
|
|
12303
13049
|
const kinds = [text2(pre).trim() && "pre-request", text2(post).trim() && "after-response"].filter(Boolean);
|
|
12304
|
-
if (kinds.length)
|
|
13050
|
+
if (kinds.length)
|
|
13051
|
+
warnings.push({
|
|
13052
|
+
item,
|
|
13053
|
+
message: `Its ${kinds.join(" and ")} ${kinds.length > 1 ? "scripts are" : "script is"} left out: an Insomnia script calls Insomnia's own "insomnia" object, which the app does not have.`
|
|
13054
|
+
});
|
|
12305
13055
|
};
|
|
12306
13056
|
const request = ({ source, name, description: description2, scripts: own3 }) => {
|
|
12307
13057
|
const item = `request "${name || "Request"}"`;
|
|
@@ -12313,7 +13063,11 @@ function builder(warnings) {
|
|
|
12313
13063
|
method: text2(field3(source, "method")),
|
|
12314
13064
|
description: description2,
|
|
12315
13065
|
// Insomnia holds the query's rows beside the address, and sends both.
|
|
12316
|
-
url: {
|
|
13066
|
+
url: {
|
|
13067
|
+
...url,
|
|
13068
|
+
query: [...url.query ?? [], ...pairs(field3(source, "parameters"), said2)],
|
|
13069
|
+
variable: pairs(field3(source, "pathParameters"), said2)
|
|
13070
|
+
},
|
|
12317
13071
|
header: pairs(field3(source, "headers"), said2),
|
|
12318
13072
|
...body2(field3(source, "body"), said2),
|
|
12319
13073
|
...auth2(field3(source, "authentication"), said2)
|
|
@@ -12329,7 +13083,14 @@ function builder(warnings) {
|
|
|
12329
13083
|
const variable = rowsOf(field3(source, "environment"), said2);
|
|
12330
13084
|
folderVariables.push(...variable.map(({ key }) => ({ item, key })));
|
|
12331
13085
|
const headers2 = pairs(field3(source, "headers"), said2).filter((header) => !header.disabled);
|
|
12332
|
-
if (headers2.length)
|
|
13086
|
+
if (headers2.length)
|
|
13087
|
+
warnings.push({
|
|
13088
|
+
item,
|
|
13089
|
+
message: `Its headers (${headers2.map((header) => header.key).join(", ").slice(
|
|
13090
|
+
0,
|
|
13091
|
+
200
|
|
13092
|
+
)}) are left out: in Insomnia a folder's headers are sent with each request in it, and the app has no headers on a folder. Add them to its requests.`
|
|
13093
|
+
});
|
|
12333
13094
|
scripts2(item, own3);
|
|
12334
13095
|
done();
|
|
12335
13096
|
return { name, description: description2, item: children, variable, ...auth2(field3(source, "authentication"), said2) };
|
|
@@ -12337,7 +13098,11 @@ function builder(warnings) {
|
|
|
12337
13098
|
const compare = (environments) => {
|
|
12338
13099
|
for (const { item, key } of folderVariables) {
|
|
12339
13100
|
const also = environments.find((entry) => entry.vars.some((row) => row.key === key));
|
|
12340
|
-
if (also)
|
|
13101
|
+
if (also)
|
|
13102
|
+
warnings.push({
|
|
13103
|
+
item,
|
|
13104
|
+
message: `Its variable ${key} is also in the environment "${also.name}". Insomnia uses the folder's value; the app uses the environment's when that environment is active.`
|
|
13105
|
+
});
|
|
12341
13106
|
}
|
|
12342
13107
|
};
|
|
12343
13108
|
return { request, folder, compare };
|
|
@@ -12349,7 +13114,10 @@ function format5(value) {
|
|
|
12349
13114
|
const tree = field3(value, "collection");
|
|
12350
13115
|
const held = field3(value, "environments");
|
|
12351
13116
|
const type = text2(field3(value, "type"));
|
|
12352
|
-
if (!Array.isArray(tree) && !isObject4(held))
|
|
13117
|
+
if (!Array.isArray(tree) && !isObject4(held))
|
|
13118
|
+
throw new ImportError(
|
|
13119
|
+
`This Insomnia file (${type.slice(0, 60)}) holds no requests and no environments, so there is nothing to import.`
|
|
13120
|
+
);
|
|
12353
13121
|
const name = text2(field3(value, "name")).trim() || UNNAMED;
|
|
12354
13122
|
const item = Array.isArray(tree) ? `collection "${name}"` : `environment "${name}"`;
|
|
12355
13123
|
const { said: said2, done } = reader(item, warnings);
|
|
@@ -12358,15 +13126,26 @@ function format5(value) {
|
|
|
12358
13126
|
if (depth > MAX_DEPTH) throw tooDeep();
|
|
12359
13127
|
return objects(items).flatMap((source) => {
|
|
12360
13128
|
const own3 = isObject4(field3(source, "scripts")) ? field3(source, "scripts") : {};
|
|
12361
|
-
const entry = {
|
|
13129
|
+
const entry = {
|
|
13130
|
+
source,
|
|
13131
|
+
name: text2(field3(source, "name")).trim(),
|
|
13132
|
+
description: described(source),
|
|
13133
|
+
scripts: { pre: field3(own3, "preRequest"), post: field3(own3, "afterResponse") }
|
|
13134
|
+
};
|
|
12362
13135
|
if (Array.isArray(field3(source, "children"))) return [folder(entry, level(field3(source, "children"), depth + 1))];
|
|
12363
13136
|
if (typeof field3(source, "method") === "string") return [request(entry)];
|
|
12364
|
-
warnings.push({
|
|
13137
|
+
warnings.push({
|
|
13138
|
+
item: `request "${entry.name || "Request"}"`,
|
|
13139
|
+
message: "It is not an HTTP request (gRPC, WebSocket or Socket.IO) and is left out."
|
|
13140
|
+
});
|
|
12365
13141
|
return [];
|
|
12366
13142
|
});
|
|
12367
13143
|
};
|
|
12368
13144
|
const base = isObject4(held) ? rowsOf(field3(held, "data"), said2) : [];
|
|
12369
|
-
const subs = objects(isObject4(held) ? field3(held, "subEnvironments") : []).map((sub) => ({
|
|
13145
|
+
const subs = objects(isObject4(held) ? field3(held, "subEnvironments") : []).map((sub) => ({
|
|
13146
|
+
name: text2(field3(sub, "name")).trim() || "Environment",
|
|
13147
|
+
vars: rowsOf(field3(sub, "data"), said2)
|
|
13148
|
+
}));
|
|
12370
13149
|
const spec = field3(value, "spec");
|
|
12371
13150
|
const contents = isObject4(spec) ? field3(spec, "contents") : void 0;
|
|
12372
13151
|
const left = [
|
|
@@ -12375,13 +13154,28 @@ function format5(value) {
|
|
|
12375
13154
|
objects(field3(value, "testSuites")).length > 0 && "unit tests",
|
|
12376
13155
|
isObject4(spec) && (typeof field3(spec, "file") === "string" || isObject4(contents) && Object.keys(contents).length > 0) && "API specification"
|
|
12377
13156
|
].filter((kind) => typeof kind === "string");
|
|
12378
|
-
if (left.length)
|
|
13157
|
+
if (left.length)
|
|
13158
|
+
warnings.push({
|
|
13159
|
+
item,
|
|
13160
|
+
message: `Its ${left.length > 1 ? `${left.slice(0, -1).join(", ")} and ${left.at(-1)} are` : `${left[0]} ${left[0].endsWith("s") ? "are" : "is"}`} left out: the app imports requests, folders and environments.`
|
|
13161
|
+
});
|
|
12379
13162
|
let result;
|
|
12380
13163
|
if (Array.isArray(tree)) {
|
|
12381
|
-
result = {
|
|
13164
|
+
result = {
|
|
13165
|
+
collections: [{ info: { name, description: described(value) }, variable: base, item: level(tree, 1) }],
|
|
13166
|
+
environments: subs.map((sub) => environment(sub.name, sub.vars)),
|
|
13167
|
+
warnings
|
|
13168
|
+
};
|
|
12382
13169
|
} else {
|
|
12383
|
-
const merged = (own3) => [
|
|
12384
|
-
|
|
13170
|
+
const merged = (own3) => [
|
|
13171
|
+
...own3,
|
|
13172
|
+
...base.filter((row) => !own3.some((other) => other.key === row.key))
|
|
13173
|
+
];
|
|
13174
|
+
result = {
|
|
13175
|
+
collections: [],
|
|
13176
|
+
environments: [environment(name, base), ...subs.map((sub) => environment(sub.name, merged(sub.vars)))],
|
|
13177
|
+
warnings
|
|
13178
|
+
};
|
|
12385
13179
|
}
|
|
12386
13180
|
done();
|
|
12387
13181
|
compare(result.environments);
|
|
@@ -12390,7 +13184,9 @@ function format5(value) {
|
|
|
12390
13184
|
function format4(value) {
|
|
12391
13185
|
const version = field3(value, "__export_format");
|
|
12392
13186
|
if (version !== 4) {
|
|
12393
|
-
throw new ImportError(
|
|
13187
|
+
throw new ImportError(
|
|
13188
|
+
`This is an Insomnia export of ${version === void 0 ? "a format with no number" : `format ${text2(version).slice(0, 20)}`}, which Wayfarer does not read. Export from a current Insomnia (format 4 or 5) and import that.`
|
|
13189
|
+
);
|
|
12394
13190
|
}
|
|
12395
13191
|
const warnings = [];
|
|
12396
13192
|
const { request, folder, compare } = builder(warnings);
|
|
@@ -12411,7 +13207,12 @@ function format4(value) {
|
|
|
12411
13207
|
return (parent === "" ? [] : children.get(parent) ?? []).flatMap((source) => {
|
|
12412
13208
|
if (placed.has(source) || kind(source) !== "request" && kind(source) !== "request_group") return [];
|
|
12413
13209
|
placed.add(source);
|
|
12414
|
-
const entry = {
|
|
13210
|
+
const entry = {
|
|
13211
|
+
source,
|
|
13212
|
+
name: text2(field3(source, "name")).trim(),
|
|
13213
|
+
description: field3(source, "description"),
|
|
13214
|
+
scripts: { pre: field3(source, "preRequestScript"), post: field3(source, "afterResponseScript") }
|
|
13215
|
+
};
|
|
12415
13216
|
return [kind(source) === "request_group" ? folder(entry, level(id(source), depth + 1)) : request(entry)];
|
|
12416
13217
|
});
|
|
12417
13218
|
};
|
|
@@ -12419,7 +13220,11 @@ function format4(value) {
|
|
|
12419
13220
|
const collections = workspaces.map((workspace) => {
|
|
12420
13221
|
const name = text2(field3(workspace, "name")).trim() || UNNAMED;
|
|
12421
13222
|
const { said: said2, done } = reader(`collection "${name}"`, warnings);
|
|
12422
|
-
const collection = {
|
|
13223
|
+
const collection = {
|
|
13224
|
+
info: { name, description: field3(workspace, "description") },
|
|
13225
|
+
variable: under(id(workspace), "environment").flatMap((base) => rowsOf(field3(base, "data"), said2)),
|
|
13226
|
+
item: level(id(workspace), 1)
|
|
13227
|
+
};
|
|
12423
13228
|
done();
|
|
12424
13229
|
return collection;
|
|
12425
13230
|
});
|
|
@@ -12432,16 +13237,38 @@ function format4(value) {
|
|
|
12432
13237
|
done();
|
|
12433
13238
|
return made;
|
|
12434
13239
|
});
|
|
12435
|
-
const known = new Set(
|
|
13240
|
+
const known = new Set(
|
|
13241
|
+
resources.filter((resource) => kind(resource) === "workspace" || kind(resource) === "request_group").map(id)
|
|
13242
|
+
);
|
|
12436
13243
|
const loose = [...children.keys()].filter((parent) => parent !== "" && !known.has(parent)).flatMap((parent) => level(parent, 1));
|
|
12437
13244
|
if (loose.length) collections.push({ info: { name: UNNAMED, description: void 0 }, variable: [], item: loose });
|
|
12438
|
-
const unplaced = resources.filter(
|
|
12439
|
-
|
|
13245
|
+
const unplaced = resources.filter(
|
|
13246
|
+
(resource) => (kind(resource) === "request" || kind(resource) === "request_group") && !placed.has(resource)
|
|
13247
|
+
).length;
|
|
13248
|
+
if (unplaced)
|
|
13249
|
+
warnings.push({
|
|
13250
|
+
item: "file",
|
|
13251
|
+
message: `${unplaced} ${unplaced === 1 ? "request or folder is" : "requests and folders are"} left out: the file does not say which collection ${unplaced === 1 ? "it is" : "they are"} in.`
|
|
13252
|
+
});
|
|
12440
13253
|
for (const resource of resources) {
|
|
12441
|
-
if (kind(resource).endsWith("_request"))
|
|
13254
|
+
if (kind(resource).endsWith("_request"))
|
|
13255
|
+
warnings.push({
|
|
13256
|
+
item: `request "${text2(field3(resource, "name")).trim() || "Request"}"`,
|
|
13257
|
+
message: "It is not an HTTP request (gRPC, WebSocket or Socket.IO) and is left out."
|
|
13258
|
+
});
|
|
12442
13259
|
}
|
|
12443
|
-
const others = [
|
|
12444
|
-
|
|
13260
|
+
const others = [
|
|
13261
|
+
...new Set(
|
|
13262
|
+
resources.filter(
|
|
13263
|
+
(resource) => !["workspace", "request", "request_group", "environment"].includes(kind(resource)) && !kind(resource).endsWith("_request") && !(kind(resource) === "cookie_jar" && objects(field3(resource, "cookies")).length === 0) && !(kind(resource) === "api_spec" && !text2(field3(resource, "contents")).trim())
|
|
13264
|
+
).map((resource) => kind(resource).slice(0, 30))
|
|
13265
|
+
)
|
|
13266
|
+
];
|
|
13267
|
+
if (others.length)
|
|
13268
|
+
warnings.push({
|
|
13269
|
+
item: "file",
|
|
13270
|
+
message: `What the file holds that is not a request, a folder or an environment is left out (${others.slice(0, 8).join(", ")}).`
|
|
13271
|
+
});
|
|
12445
13272
|
compare(environments);
|
|
12446
13273
|
return { collections, environments, warnings };
|
|
12447
13274
|
}
|
|
@@ -12487,13 +13314,19 @@ function openApiCollection(root) {
|
|
|
12487
13314
|
const swagger = field3(root, "swagger") !== void 0;
|
|
12488
13315
|
const version = text2(field3(root, swagger ? "swagger" : "openapi")).slice(0, 20);
|
|
12489
13316
|
if (swagger ? !/^2(\.0)?$/.test(version) : !/^3(\.|$)/.test(version)) {
|
|
12490
|
-
throw new ImportError(
|
|
13317
|
+
throw new ImportError(
|
|
13318
|
+
`This is ${swagger ? "a Swagger" : "an OpenAPI"} description of version ${version || "none"}, which Wayfarer does not read. It reads OpenAPI 3.0 and 3.1 and Swagger 2.0.`
|
|
13319
|
+
);
|
|
12491
13320
|
}
|
|
12492
13321
|
const warnings = [];
|
|
12493
13322
|
const info = field3(root, "info");
|
|
12494
13323
|
const name = text2(isObject4(info) ? field3(info, "title") : "").trim() || UNNAMED2;
|
|
12495
13324
|
const top = `collection "${name}"`;
|
|
12496
|
-
if (!swagger && !/^3(\.[01](\.|$)|$)/.test(version))
|
|
13325
|
+
if (!swagger && !/^3(\.[01](\.|$)|$)/.test(version))
|
|
13326
|
+
warnings.push({
|
|
13327
|
+
item: top,
|
|
13328
|
+
message: `The file is written for OpenAPI ${version}: it is read as OpenAPI 3.1, and what is newer is left out.`
|
|
13329
|
+
});
|
|
12497
13330
|
let item = top;
|
|
12498
13331
|
const unread = /* @__PURE__ */ new Map();
|
|
12499
13332
|
const note = (kind, ref) => {
|
|
@@ -12542,7 +13375,8 @@ function openApiCollection(root) {
|
|
|
12542
13375
|
const of2 = (raw, depth) => {
|
|
12543
13376
|
const schema = resolve(raw);
|
|
12544
13377
|
if (!isObject4(schema) || left-- <= 0 || depth > MAX_DEPTH || open.has(schema)) return null;
|
|
12545
|
-
for (const key of ["example", "default", "const"])
|
|
13378
|
+
for (const key of ["example", "default", "const"])
|
|
13379
|
+
if (field3(schema, key) !== void 0) return field3(schema, key);
|
|
12546
13380
|
for (const key of ["examples", "enum"]) if (list3(field3(schema, key)).length) return list3(field3(schema, key))[0];
|
|
12547
13381
|
open.add(schema);
|
|
12548
13382
|
try {
|
|
@@ -12555,7 +13389,8 @@ function openApiCollection(root) {
|
|
|
12555
13389
|
});
|
|
12556
13390
|
parts.push(Object.fromEntries(sent.map((key) => [key, of2(field3(properties, key), depth + 1)])));
|
|
12557
13391
|
}
|
|
12558
|
-
if (parts.length)
|
|
13392
|
+
if (parts.length)
|
|
13393
|
+
return parts.some(isObject4) ? Object.fromEntries(parts.filter(isObject4).flatMap((part) => Object.entries(part))) : parts[0];
|
|
12559
13394
|
const choice = [...list3(field3(schema, "oneOf")), ...list3(field3(schema, "anyOf"))][0];
|
|
12560
13395
|
if (choice !== void 0) return of2(choice, depth + 1);
|
|
12561
13396
|
const types = field3(schema, "type");
|
|
@@ -12581,20 +13416,43 @@ function openApiCollection(root) {
|
|
|
12581
13416
|
const schema = resolve(raw);
|
|
12582
13417
|
return isObject4(schema) && (field3(schema, "type") === "file" || field3(schema, "format") === "binary");
|
|
12583
13418
|
};
|
|
12584
|
-
const form = (type, fields) => type.startsWith("multipart/") ? {
|
|
13419
|
+
const form = (type, fields) => type.startsWith("multipart/") ? {
|
|
13420
|
+
mode: "formdata",
|
|
13421
|
+
formdata: fields.map(
|
|
13422
|
+
({ key, schema, value }) => isFile(schema) ? { key, type: "file", src: "" } : { key, type: "text", value: said(value) }
|
|
13423
|
+
)
|
|
13424
|
+
} : { mode: "urlencoded", urlencoded: fields.map(({ key, value }) => ({ key, value: said(value) })) };
|
|
12585
13425
|
const bodyOf2 = (type, media) => {
|
|
12586
13426
|
const kind = type.toLowerCase();
|
|
12587
13427
|
const schema = resolve(field3(media, "schema"));
|
|
12588
13428
|
const value = example(media) ?? sample(schema, true);
|
|
12589
13429
|
if (kind.startsWith("multipart/") || kind === "application/x-www-form-urlencoded") {
|
|
12590
13430
|
const properties = isObject4(schema) ? field3(schema, "properties") : void 0;
|
|
12591
|
-
return {
|
|
13431
|
+
return {
|
|
13432
|
+
body: form(
|
|
13433
|
+
kind,
|
|
13434
|
+
Object.keys(isObject4(value) ? value : {}).map((key) => ({
|
|
13435
|
+
key,
|
|
13436
|
+
schema: isObject4(properties) ? field3(properties, key) : void 0,
|
|
13437
|
+
value: value[key]
|
|
13438
|
+
}))
|
|
13439
|
+
),
|
|
13440
|
+
header: []
|
|
13441
|
+
};
|
|
12592
13442
|
}
|
|
12593
|
-
if (kind === "application/octet-stream" || isFile(schema))
|
|
13443
|
+
if (kind === "application/octet-stream" || isFile(schema))
|
|
13444
|
+
return { body: { mode: "file", file: { src: "" } }, header: [] };
|
|
12594
13445
|
const language = /json|\*/.test(kind) ? "json" : kind.includes("xml") ? "xml" : kind.includes("html") ? "html" : kind.includes("javascript") ? "javascript" : "text";
|
|
12595
13446
|
const raw = typeof value === "string" ? value : language === "json" && value !== null ? JSON.stringify(value, null, 2) : "";
|
|
12596
|
-
if (!raw)
|
|
12597
|
-
|
|
13447
|
+
if (!raw)
|
|
13448
|
+
warnings.push({
|
|
13449
|
+
item,
|
|
13450
|
+
message: `Its body (${type.slice(0, 80)}) has no example in the file that the app can write out: the body is empty.`
|
|
13451
|
+
});
|
|
13452
|
+
return {
|
|
13453
|
+
body: { mode: "raw", raw, options: { raw: { language } } },
|
|
13454
|
+
header: kind.includes("*") || RAW_CONTENT_TYPES[language] === kind ? [] : [{ key: "Content-Type", value: type }]
|
|
13455
|
+
};
|
|
12598
13456
|
};
|
|
12599
13457
|
const components = field3(root, "components");
|
|
12600
13458
|
const schemes = swagger ? field3(root, "securityDefinitions") : isObject4(components) ? field3(components, "securitySchemes") : void 0;
|
|
@@ -12602,16 +13460,22 @@ function openApiCollection(root) {
|
|
|
12602
13460
|
if (!Array.isArray(requirements)) return {};
|
|
12603
13461
|
const names = Object.keys(requirements.find(isObject4) ?? {});
|
|
12604
13462
|
if (!names.length) return { auth: { type: "noauth" } };
|
|
12605
|
-
if (names.length > 1)
|
|
13463
|
+
if (names.length > 1)
|
|
13464
|
+
warnings.push({
|
|
13465
|
+
item,
|
|
13466
|
+
message: `It needs ${names.length} security schemes together (${named(names)}): only the first is set.`
|
|
13467
|
+
});
|
|
12606
13468
|
const scheme = resolve(isObject4(schemes) ? field3(schemes, names[0]) : void 0);
|
|
12607
13469
|
if (!isObject4(scheme)) return { auth: { type: `${names[0].slice(0, 60)}, which the file does not describe` } };
|
|
12608
13470
|
const type = text2(field3(scheme, "type"));
|
|
12609
13471
|
const how = text2(field3(scheme, "scheme")).toLowerCase();
|
|
12610
|
-
if (type === "basic" || type === "http" && how === "basic")
|
|
13472
|
+
if (type === "basic" || type === "http" && how === "basic")
|
|
13473
|
+
return { auth: { type: "basic", basic: { username: "{{authUsername}}", password: "{{authPassword}}" } } };
|
|
12611
13474
|
if (type === "http" && how === "bearer") return { auth: { type: "bearer", bearer: { token: "{{bearerToken}}" } } };
|
|
12612
13475
|
if (type === "apiKey") {
|
|
12613
13476
|
const place = text2(field3(scheme, "in"));
|
|
12614
|
-
if (place !== "header" && place !== "query")
|
|
13477
|
+
if (place !== "header" && place !== "query")
|
|
13478
|
+
return { auth: { type: `an API key in a ${place.slice(0, 20) || "place the file does not say"}` } };
|
|
12615
13479
|
return { auth: { type: "apikey", apikey: { key: text2(field3(scheme, "name")), value: "{{apiKey}}", in: place } } };
|
|
12616
13480
|
}
|
|
12617
13481
|
if (type === "oauth2") {
|
|
@@ -12621,7 +13485,15 @@ function openApiCollection(root) {
|
|
|
12621
13485
|
const client = flow("clientCredentials", "application");
|
|
12622
13486
|
const password = flow("password", "password");
|
|
12623
13487
|
const chosen = code ?? client ?? password;
|
|
12624
|
-
if (!isObject4(chosen))
|
|
13488
|
+
if (!isObject4(chosen))
|
|
13489
|
+
return {
|
|
13490
|
+
auth: {
|
|
13491
|
+
type,
|
|
13492
|
+
oauth2: {
|
|
13493
|
+
grant_type: (swagger ? text2(field3(scheme, "flow")) : Object.keys(isObject4(flows) ? flows : {})[0]) || "none said"
|
|
13494
|
+
}
|
|
13495
|
+
}
|
|
13496
|
+
};
|
|
12625
13497
|
const asked = field3(requirements.find(isObject4) ?? {}, names[0]);
|
|
12626
13498
|
const scopes = field3(chosen, "scopes");
|
|
12627
13499
|
const scope = (Array.isArray(asked) && asked.length ? asked.map(text2) : isObject4(scopes) ? Object.keys(scopes) : []).join(" ");
|
|
@@ -12654,14 +13526,24 @@ function openApiCollection(root) {
|
|
|
12654
13526
|
return withoutEndSlash(filled);
|
|
12655
13527
|
};
|
|
12656
13528
|
const host = text2(field3(root, "host"));
|
|
12657
|
-
const base = swagger ? withoutEndSlash(
|
|
13529
|
+
const base = swagger ? withoutEndSlash(
|
|
13530
|
+
`${host && `${text2(list3(field3(root, "schemes"))[0] ?? "https")}://${host}`}${text2(field3(root, "basePath"))}`
|
|
13531
|
+
) : server(field3(root, "servers")) ?? "";
|
|
12658
13532
|
if (!/^([a-z][a-z0-9+.-]*:\/\/|\{\{)/i.test(base)) {
|
|
12659
|
-
warnings.push({
|
|
13533
|
+
warnings.push({
|
|
13534
|
+
item: top,
|
|
13535
|
+
message: `The file gives no full address for its server (${base.slice(0, 80) || "it names none"}): give the collection's variable baseUrl the server's address before you send.`
|
|
13536
|
+
});
|
|
12660
13537
|
}
|
|
12661
13538
|
const others = list3(field3(root, "servers")).length - 1;
|
|
12662
|
-
if (!swagger && others > 0)
|
|
13539
|
+
if (!swagger && others > 0)
|
|
13540
|
+
warnings.push({
|
|
13541
|
+
item: top,
|
|
13542
|
+
message: `The file names ${others + 1} servers: the collection's variable baseUrl holds the first (${base.slice(0, 80)}).`
|
|
13543
|
+
});
|
|
12663
13544
|
const folders = /* @__PURE__ */ new Map();
|
|
12664
|
-
for (const tag of list3(field3(root, "tags")).filter(isObject4))
|
|
13545
|
+
for (const tag of list3(field3(root, "tags")).filter(isObject4))
|
|
13546
|
+
folders.set(text2(field3(tag, "name")), { description: field3(tag, "description"), item: [] });
|
|
12665
13547
|
const loose = [];
|
|
12666
13548
|
const paths = field3(root, "paths");
|
|
12667
13549
|
for (const path of isObject4(paths) ? Object.keys(paths) : []) {
|
|
@@ -12674,7 +13556,8 @@ function openApiCollection(root) {
|
|
|
12674
13556
|
const requestName = text2(field3(operation, "summary")).trim() || text2(field3(operation, "operationId")).trim() || `${method.toUpperCase()} ${path}`;
|
|
12675
13557
|
item = `request "${requestName}"`;
|
|
12676
13558
|
const parameters = /* @__PURE__ */ new Map();
|
|
12677
|
-
for (const parameter of [...resolved(field3(pathItem, "parameters")), ...resolved(field3(operation, "parameters"))])
|
|
13559
|
+
for (const parameter of [...resolved(field3(pathItem, "parameters")), ...resolved(field3(operation, "parameters"))])
|
|
13560
|
+
parameters.set(`${text2(field3(parameter, "in"))}:${text2(field3(parameter, "name"))}`, parameter);
|
|
12678
13561
|
const query = [];
|
|
12679
13562
|
const header = [];
|
|
12680
13563
|
const cookies = [];
|
|
@@ -12691,7 +13574,12 @@ function openApiCollection(root) {
|
|
|
12691
13574
|
if (place === "formData") fields.push({ key, schema: parameter, value });
|
|
12692
13575
|
else if (place === "query") query.push({ key, value: said(value) });
|
|
12693
13576
|
else if (place === "cookie") cookies.push(`${key}=${said(value)}`);
|
|
12694
|
-
else if (place === "header" && !/^(accept|content-type|authorization)$/i.test(key))
|
|
13577
|
+
else if (place === "header" && !/^(accept|content-type|authorization)$/i.test(key))
|
|
13578
|
+
header.push({
|
|
13579
|
+
key,
|
|
13580
|
+
value: said(value),
|
|
13581
|
+
...field3(parameter, "required") !== true && said(value) === "" && { disabled: true }
|
|
13582
|
+
});
|
|
12695
13583
|
}
|
|
12696
13584
|
if (cookies.length) header.push({ key: "Cookie", value: cookies.join("; ") });
|
|
12697
13585
|
let body3;
|
|
@@ -12699,19 +13587,30 @@ function openApiCollection(root) {
|
|
|
12699
13587
|
const requestBody = resolve(field3(operation, "requestBody"));
|
|
12700
13588
|
const content = isObject4(requestBody) ? field3(requestBody, "content") : void 0;
|
|
12701
13589
|
if (fields.length) {
|
|
12702
|
-
body3 = form(
|
|
13590
|
+
body3 = form(
|
|
13591
|
+
consumes.some((type) => type.startsWith("multipart/")) || fields.some(({ schema }) => isFile(schema)) ? "multipart/form-data" : "application/x-www-form-urlencoded",
|
|
13592
|
+
fields
|
|
13593
|
+
);
|
|
12703
13594
|
} else if (inBody || isObject4(content)) {
|
|
12704
13595
|
const types = inBody ? consumes.length ? consumes : ["application/json"] : Object.keys(content).filter((type2) => isObject4(field3(content, type2)));
|
|
12705
13596
|
const type = types.find((entry) => /json/i.test(entry)) ?? types[0];
|
|
12706
13597
|
if (type !== void 0) {
|
|
12707
|
-
if (!inBody && types.length > 1)
|
|
13598
|
+
if (!inBody && types.length > 1)
|
|
13599
|
+
warnings.push({
|
|
13600
|
+
item,
|
|
13601
|
+
message: `Its body is described in ${types.length} media types (${named(types)}): the request has the ${type.slice(0, 80)} one.`
|
|
13602
|
+
});
|
|
12708
13603
|
const made2 = bodyOf2(type, inBody ?? field3(content, type));
|
|
12709
13604
|
body3 = made2.body;
|
|
12710
13605
|
header.push(...made2.header);
|
|
12711
13606
|
}
|
|
12712
13607
|
}
|
|
12713
13608
|
const tags = list3(field3(operation, "tags")).map(text2).filter(Boolean);
|
|
12714
|
-
if (tags.length > 1)
|
|
13609
|
+
if (tags.length > 1)
|
|
13610
|
+
warnings.push({
|
|
13611
|
+
item,
|
|
13612
|
+
message: `It has ${tags.length} tags (${named(tags)}): it is in the folder of the first.`
|
|
13613
|
+
});
|
|
12715
13614
|
const own3 = server(field3(operation, "servers")) ?? server(field3(pathItem, "servers"));
|
|
12716
13615
|
const request = {
|
|
12717
13616
|
name: requestName,
|
|
@@ -12719,7 +13618,11 @@ function openApiCollection(root) {
|
|
|
12719
13618
|
method: method.toUpperCase(),
|
|
12720
13619
|
description: field3(operation, "description"),
|
|
12721
13620
|
// A `{name}` of the path is the variable `{{name}}`, which the composer asks for before it sends.
|
|
12722
|
-
url: {
|
|
13621
|
+
url: {
|
|
13622
|
+
host: [own3 ?? "{{baseUrl}}"],
|
|
13623
|
+
path: path.replace(/^\//, "").split("/").map((part) => part.replace(/\{([^{}]+)\}/g, "{{$1}}")),
|
|
13624
|
+
query
|
|
13625
|
+
},
|
|
12723
13626
|
header,
|
|
12724
13627
|
...body3 && { body: body3 },
|
|
12725
13628
|
...auth3(field3(operation, "security"))
|
|
@@ -12731,48 +13634,46 @@ function openApiCollection(root) {
|
|
|
12731
13634
|
}
|
|
12732
13635
|
}
|
|
12733
13636
|
item = top;
|
|
12734
|
-
const made = {
|
|
13637
|
+
const made = {
|
|
13638
|
+
info: { name, description: isObject4(info) ? field3(info, "description") : void 0 },
|
|
13639
|
+
variable: [{ key: "baseUrl", value: base }],
|
|
13640
|
+
...auth3(field3(root, "security"))
|
|
13641
|
+
};
|
|
12735
13642
|
const tagged = [...folders].filter(([, folder]) => folder.item.length).map(([folderName, folder]) => ({ name: folderName, description: folder.description, item: folder.item }));
|
|
12736
|
-
if (!tagged.length && !loose.length)
|
|
13643
|
+
if (!tagged.length && !loose.length)
|
|
13644
|
+
throw new ImportError("This OpenAPI file describes no operation under `paths`, so there is no request to import.");
|
|
12737
13645
|
for (const [where, { outside, missing }] of unread) {
|
|
12738
|
-
if (outside.size)
|
|
12739
|
-
|
|
13646
|
+
if (outside.size)
|
|
13647
|
+
warnings.push({
|
|
13648
|
+
item: where,
|
|
13649
|
+
message: `It refers to ${outside.size === 1 ? "another file or an address" : `${outside.size} other files or addresses`} (${named([...outside])}). An import fetches nothing: what ${outside.size === 1 ? "it describes" : "they describe"} is left out.`
|
|
13650
|
+
});
|
|
13651
|
+
if (missing.size)
|
|
13652
|
+
warnings.push({
|
|
13653
|
+
item: where,
|
|
13654
|
+
message: `It refers to ${missing.size === 1 ? "a part" : `${missing.size} parts`} of the file that ${missing.size === 1 ? "is" : "are"} not there or ${missing.size === 1 ? "leads" : "lead"} back to ${missing.size === 1 ? "itself" : "themselves"} (${named([...missing])}), and ${missing.size === 1 ? "that is" : "those are"} left out.`
|
|
13655
|
+
});
|
|
12740
13656
|
}
|
|
12741
13657
|
return { collection: { ...made, item: [...tagged, ...loose] }, warnings };
|
|
12742
13658
|
}
|
|
12743
13659
|
|
|
12744
|
-
// ../core/src/import/script-scan.ts
|
|
12745
|
-
var START = String.raw`(?<![\w$.])`;
|
|
12746
|
-
var escape = (text3) => text3.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
12747
|
-
var member = (name) => ({ api: name, finds: [[name, new RegExp(`${START}${escape(name)}(?![\\w$])`)]] });
|
|
12748
|
-
var required = (module) => [`require('${module}')`, new RegExp(`${START}require\\(\\s*["']${escape(module)}["']\\s*\\)`)];
|
|
12749
|
-
var called = (name) => [name, new RegExp(`${START}${escape(name)}\\s*\\(`)];
|
|
12750
|
-
var used = (name) => [name, new RegExp(`${START}${escape(name)}\\s*[.(]`)];
|
|
12751
|
-
var SCRIPT_SCAN = [
|
|
12752
|
-
...["pm.cookies.get", "pm.cookies.has", "pm.cookies.toObject", "pm.cookies.jar", "pm.visualizer.set", "pm.vault.get", "pm.require", "postman.getResponseCookie"].map(member),
|
|
12753
|
-
{ api: "legacy library globals (_, CryptoJS, tv4, cheerio, xml2Json, Backbone)", finds: ["tv4", "cheerio", "xml2Json", "Backbone"].map(used) },
|
|
12754
|
-
{ api: "clearTimeout, setInterval, clearInterval", finds: ["clearTimeout", "setInterval", "clearInterval"].map(called) },
|
|
12755
|
-
...["ajv", "cheerio", "csv-parse/lib/sync", "postman-collection", "tv4", "xml2js"].map((module) => ({ api: `require('${module}')`, finds: [required(module)] })),
|
|
12756
|
-
{
|
|
12757
|
-
api: "require of a Node module (buffer, events, path, querystring, stream, string_decoder, timers, url, util)",
|
|
12758
|
-
finds: ["buffer", "events", "path", "querystring", "stream", "string_decoder", "timers", "url", "util"].map(required)
|
|
12759
|
-
}
|
|
12760
|
-
];
|
|
12761
|
-
function unsupportedApis(script) {
|
|
12762
|
-
return SCRIPT_SCAN.flatMap((entry) => entry.finds).filter(([, pattern]) => pattern.test(script)).map(([label]) => label);
|
|
12763
|
-
}
|
|
12764
|
-
|
|
12765
13660
|
// ../core/src/import/yaml.ts
|
|
12766
13661
|
var import_yaml = __toESM(require_dist(), 1);
|
|
12767
13662
|
var MAX_YAML_ALIASES = 100;
|
|
12768
13663
|
var MAX_YAML_CHARS = 1024 * 1024;
|
|
12769
13664
|
var YAML_ALIASES = `The YAML file repeats parts of itself through aliases ${MAX_YAML_ALIASES} times or more and is refused: a file written that way grows without end as it is read.`;
|
|
12770
|
-
var YAML_TOO_LARGE = "
|
|
13665
|
+
var YAML_TOO_LARGE = "This file is not JSON, and a YAML file can be 1 MB at most: reading YAML takes far more memory than JSON. Convert it to JSON and import that.";
|
|
12771
13666
|
var NOT_READABLE = "The file is neither JSON nor YAML that can be read, so it is not a file Wayfarer can import.";
|
|
12772
13667
|
function parseYaml(text3) {
|
|
12773
13668
|
if (text3.length > MAX_YAML_CHARS) throw new ImportError(YAML_TOO_LARGE);
|
|
12774
13669
|
try {
|
|
12775
|
-
return (0, import_yaml.parse)(text3, {
|
|
13670
|
+
return (0, import_yaml.parse)(text3, {
|
|
13671
|
+
maxAliasCount: MAX_YAML_ALIASES,
|
|
13672
|
+
schema: "core",
|
|
13673
|
+
logLevel: "error",
|
|
13674
|
+
uniqueKeys: false,
|
|
13675
|
+
prettyErrors: false
|
|
13676
|
+
});
|
|
12776
13677
|
} catch (error) {
|
|
12777
13678
|
throw new ImportError(error instanceof Error && /alias count/i.test(error.message) ? YAML_ALIASES : NOT_READABLE);
|
|
12778
13679
|
}
|
|
@@ -12812,7 +13713,12 @@ function scriptWarnings(item, scripts2) {
|
|
|
12812
13713
|
const found = unsupportedApis(scripts2[kind]);
|
|
12813
13714
|
if (!found.length) return [];
|
|
12814
13715
|
const names = found.length > 1 ? `${found.slice(0, -1).join(", ")} and ${found.at(-1)}` : found[0];
|
|
12815
|
-
return [
|
|
13716
|
+
return [
|
|
13717
|
+
{
|
|
13718
|
+
item,
|
|
13719
|
+
message: `Its ${kind === "pre" ? "pre-request" : "post-response"} script uses ${names}, which Wayfarer does not have, so the script stops with an error there.`
|
|
13720
|
+
}
|
|
13721
|
+
];
|
|
12816
13722
|
});
|
|
12817
13723
|
}
|
|
12818
13724
|
function inheritedScriptWarnings(payload) {
|
|
@@ -12827,9 +13733,13 @@ function curlToRequest(text3) {
|
|
|
12827
13733
|
const parsed2 = parseCurl(text3);
|
|
12828
13734
|
const { content, warnings } = curlRequest(parsed2);
|
|
12829
13735
|
const issues = validateRequestContent(content, "request");
|
|
12830
|
-
if (issues.length) throw new ImportError("
|
|
13736
|
+
if (issues.length) throw new ImportError("This cURL command does not describe a request Wayfarer can send.", issues);
|
|
12831
13737
|
const address2 = URL.parse(parsed2.request.url);
|
|
12832
|
-
return {
|
|
13738
|
+
return {
|
|
13739
|
+
name: `${content.method} ${address2 ? address2.host + address2.pathname : parsed2.request.url}`,
|
|
13740
|
+
content,
|
|
13741
|
+
warnings
|
|
13742
|
+
};
|
|
12833
13743
|
}
|
|
12834
13744
|
function built(format, name, requests) {
|
|
12835
13745
|
const now = Date.now();
|
|
@@ -12838,7 +13748,15 @@ function built(format, name, requests) {
|
|
|
12838
13748
|
const payload = {
|
|
12839
13749
|
$id: COLLECTION_FORMAT,
|
|
12840
13750
|
meta: top,
|
|
12841
|
-
collection: {
|
|
13751
|
+
collection: {
|
|
13752
|
+
id: top.id,
|
|
13753
|
+
meta: top,
|
|
13754
|
+
name,
|
|
13755
|
+
order: 0,
|
|
13756
|
+
variables: [],
|
|
13757
|
+
auth: { type: "none" },
|
|
13758
|
+
scripts: { pre: "", post: "" }
|
|
13759
|
+
},
|
|
12842
13760
|
folders: [],
|
|
12843
13761
|
requests: requests.map(({ name: requestName, content }, order) => {
|
|
12844
13762
|
const own3 = meta();
|
|
@@ -12850,7 +13768,9 @@ function built(format, name, requests) {
|
|
|
12850
13768
|
format,
|
|
12851
13769
|
formatName: IMPORT_FORMATS[format],
|
|
12852
13770
|
counts: { collections: 1, folders: 0, requests: requests.length, environments: 0 },
|
|
12853
|
-
warnings: requests.flatMap(
|
|
13771
|
+
warnings: requests.flatMap(
|
|
13772
|
+
({ name: requestName, warnings }) => warnings.map((message) => ({ item: `request "${requestName}"`, message }))
|
|
13773
|
+
)
|
|
12854
13774
|
},
|
|
12855
13775
|
collections: [added(payload)],
|
|
12856
13776
|
environments: []
|
|
@@ -12858,10 +13778,57 @@ function built(format, name, requests) {
|
|
|
12858
13778
|
}
|
|
12859
13779
|
function added(payload) {
|
|
12860
13780
|
const { payload: checked, plan, errors } = importCollection(payload, { duplicateAsNew: true });
|
|
12861
|
-
if (!checked || !plan) throw new ImportError(
|
|
13781
|
+
if (!checked || !plan) throw new ImportError("This file holds no request Wayfarer can read.", errors ?? []);
|
|
12862
13782
|
return { payload: checked, plan };
|
|
12863
13783
|
}
|
|
12864
13784
|
function importText(text3, options = {}) {
|
|
13785
|
+
const imported = readText(text3, options);
|
|
13786
|
+
return {
|
|
13787
|
+
...imported,
|
|
13788
|
+
report: {
|
|
13789
|
+
...imported.report,
|
|
13790
|
+
warnings: [...imported.report.warnings, ...unsetVariableWarnings(imported.collections, imported.environments)]
|
|
13791
|
+
}
|
|
13792
|
+
};
|
|
13793
|
+
}
|
|
13794
|
+
var PLAIN_VARIABLE = /^[\w.-]+$/;
|
|
13795
|
+
var PLACEHOLDERS = /{{([^{}]*)}}/g;
|
|
13796
|
+
var SCRIPT_SETS = /\.set\(\s*["'`]([^"'`]+)["'`]/g;
|
|
13797
|
+
function unsetVariableWarnings(collections, environments) {
|
|
13798
|
+
return collections.flatMap(({ payload }) => {
|
|
13799
|
+
const set = /* @__PURE__ */ new Set([
|
|
13800
|
+
...[payload.collection, ...payload.folders].flatMap((holder) => holder.variables.map((row) => row.key)),
|
|
13801
|
+
...environments.flatMap((environment2) => environment2.vars.map((row) => row.key))
|
|
13802
|
+
]);
|
|
13803
|
+
const scripts2 = [payload.collection, ...payload.folders, ...payload.requests].flatMap(({ scripts: scripts3 }) => [
|
|
13804
|
+
scripts3.pre,
|
|
13805
|
+
scripts3.post
|
|
13806
|
+
]);
|
|
13807
|
+
for (const script of scripts2) for (const [, name] of script.matchAll(SCRIPT_SETS)) set.add(name);
|
|
13808
|
+
const uses = payload.requests.map(({ url, headers: headers2, body: body3, auth: auth3 }) => {
|
|
13809
|
+
const text3 = JSON.stringify({ url, headers: headers2, body: body3, auth: auth3 });
|
|
13810
|
+
return new Set(
|
|
13811
|
+
[...text3.matchAll(PLACEHOLDERS)].map(([, name]) => name.trim()).filter((name) => PLAIN_VARIABLE.test(name))
|
|
13812
|
+
);
|
|
13813
|
+
});
|
|
13814
|
+
const unset = [...new Set(uses.flatMap((names2) => [...names2]))].filter((name) => !set.has(name)).sort();
|
|
13815
|
+
if (!unset.length) return [];
|
|
13816
|
+
const using = uses.filter((names2) => unset.some((name) => names2.has(name))).length;
|
|
13817
|
+
const total = payload.requests.length;
|
|
13818
|
+
const listed = unset.slice(0, unset.length > 5 ? 5 : unset.length).map((name) => `{{${name}}}`);
|
|
13819
|
+
if (unset.length > 5) listed.push(`${unset.length - 5} more`);
|
|
13820
|
+
const names = listed.length < 2 ? listed[0] : `${listed.slice(0, -1).join(", ")} and ${listed.at(-1)}`;
|
|
13821
|
+
const which = using === total ? total === 1 ? "its request uses" : total === 2 ? "both requests use" : `all ${total} requests use` : `${using} of its ${total} requests ${using === 1 ? "uses" : "use"}`;
|
|
13822
|
+
const it = unset.length === 1 ? "it" : "them";
|
|
13823
|
+
return [
|
|
13824
|
+
{
|
|
13825
|
+
item: `collection "${payload.collection.name}"`,
|
|
13826
|
+
message: `${names} ${unset.length === 1 ? "has" : "have"} no value in the file, and ${which} ${it}: give ${it} a value in an environment or in the collection's settings before you send.`
|
|
13827
|
+
}
|
|
13828
|
+
];
|
|
13829
|
+
});
|
|
13830
|
+
}
|
|
13831
|
+
function readText(text3, options) {
|
|
12865
13832
|
if (isOversizedImport(text3)) throw new ImportError(IMPORT_TOO_LARGE);
|
|
12866
13833
|
if (text3.startsWith("\uFEFF")) text3 = text3.slice(1);
|
|
12867
13834
|
if (isCurlCommand(text3)) {
|
|
@@ -12873,8 +13840,12 @@ function importText(text3, options = {}) {
|
|
|
12873
13840
|
if (!json.ok && (typeof value !== "object" || value === null)) throw new ImportError(NOT_READABLE);
|
|
12874
13841
|
const format = detectFormat(value);
|
|
12875
13842
|
if (!format) {
|
|
12876
|
-
if (of(idOf(value), "wayfarer/workspace"))
|
|
12877
|
-
|
|
13843
|
+
if (of(idOf(value), "wayfarer/workspace"))
|
|
13844
|
+
throw new ImportError("This is a workspace backup, which replaces everything. Restore it in Settings.");
|
|
13845
|
+
if (isPostmanV1(value))
|
|
13846
|
+
throw new ImportError(
|
|
13847
|
+
"This is a Postman Collection v1 file, which Wayfarer does not read. In Postman, export the collection as Collection v2.1 and import that."
|
|
13848
|
+
);
|
|
12878
13849
|
throw new ImportError(UNKNOWN);
|
|
12879
13850
|
}
|
|
12880
13851
|
const report = (counts2, warnings) => ({
|
|
@@ -12884,7 +13855,10 @@ function importText(text3, options = {}) {
|
|
|
12884
13855
|
warnings
|
|
12885
13856
|
});
|
|
12886
13857
|
if (format === "har") {
|
|
12887
|
-
const requests = parseHar(value).map(({ name, request, warnings }) => ({
|
|
13858
|
+
const requests = parseHar(value).map(({ name, request, warnings }) => ({
|
|
13859
|
+
name,
|
|
13860
|
+
...curlRequest({ request, warnings })
|
|
13861
|
+
}));
|
|
12888
13862
|
return built("har", "Imported from HAR", requests);
|
|
12889
13863
|
}
|
|
12890
13864
|
if (format === "postman-collection" || format === "openapi") {
|
|
@@ -12892,7 +13866,11 @@ function importText(text3, options = {}) {
|
|
|
12892
13866
|
const { payload: payload2, warnings } = postmanCollection(source.collection);
|
|
12893
13867
|
const collection = added(payload2);
|
|
12894
13868
|
return {
|
|
12895
|
-
report: report({ collections: 1, folders: payload2.folders.length, requests: payload2.requests.length }, [
|
|
13869
|
+
report: report({ collections: 1, folders: payload2.folders.length, requests: payload2.requests.length }, [
|
|
13870
|
+
...source.warnings,
|
|
13871
|
+
...warnings,
|
|
13872
|
+
...inheritedScriptWarnings(collection.payload)
|
|
13873
|
+
]),
|
|
12896
13874
|
collections: [collection],
|
|
12897
13875
|
environments: []
|
|
12898
13876
|
};
|
|
@@ -12905,10 +13883,15 @@ function importText(text3, options = {}) {
|
|
|
12905
13883
|
if (issues.length) throw new ImportError("The file's environments are not ones the app can hold.", issues);
|
|
12906
13884
|
const count = (of2) => collections.reduce((sum, { payload: payload2 }) => sum + of2(payload2).length, 0);
|
|
12907
13885
|
return {
|
|
12908
|
-
report: report(
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
13886
|
+
report: report(
|
|
13887
|
+
{
|
|
13888
|
+
collections: collections.length,
|
|
13889
|
+
folders: count((payload2) => payload2.folders),
|
|
13890
|
+
requests: count((payload2) => payload2.requests),
|
|
13891
|
+
environments: environments.length
|
|
13892
|
+
},
|
|
13893
|
+
[...warnings, ...read.flatMap((collection) => collection.warnings)]
|
|
13894
|
+
),
|
|
12912
13895
|
collections,
|
|
12913
13896
|
environments
|
|
12914
13897
|
};
|
|
@@ -12921,12 +13904,28 @@ function importText(text3, options = {}) {
|
|
|
12921
13904
|
}
|
|
12922
13905
|
if (format === "wayfarer-environments") {
|
|
12923
13906
|
const result = validateEnvironmentExport(value);
|
|
12924
|
-
if (!result.payload)
|
|
12925
|
-
|
|
13907
|
+
if (!result.payload)
|
|
13908
|
+
throw new ImportError(
|
|
13909
|
+
"The file is not a valid Wayfarer environments file.",
|
|
13910
|
+
(result.errors ?? []).map((message) => ({ path: "", message }))
|
|
13911
|
+
);
|
|
13912
|
+
return {
|
|
13913
|
+
report: report({ environments: result.payload.length }, []),
|
|
13914
|
+
collections: [],
|
|
13915
|
+
environments: result.payload
|
|
13916
|
+
};
|
|
12926
13917
|
}
|
|
12927
13918
|
const { payload, plan, errors } = importCollection(value, options);
|
|
12928
|
-
if (!payload || !plan)
|
|
12929
|
-
|
|
13919
|
+
if (!payload || !plan)
|
|
13920
|
+
throw new ImportError("This file is not a Wayfarer collection. What does not match is listed below.", errors ?? []);
|
|
13921
|
+
return {
|
|
13922
|
+
report: report(
|
|
13923
|
+
{ collections: 1, folders: payload.folders.length, requests: payload.requests.length },
|
|
13924
|
+
inheritedScriptWarnings(payload)
|
|
13925
|
+
),
|
|
13926
|
+
collections: [{ payload, plan }],
|
|
13927
|
+
environments: []
|
|
13928
|
+
};
|
|
12930
13929
|
}
|
|
12931
13930
|
|
|
12932
13931
|
// ../core/src/run/report.ts
|
|
@@ -12939,10 +13938,21 @@ var counts = (results) => ({
|
|
|
12939
13938
|
errors: results.filter((result) => result.error !== void 0).length
|
|
12940
13939
|
});
|
|
12941
13940
|
function runReportJson(report, info) {
|
|
12942
|
-
return {
|
|
13941
|
+
return {
|
|
13942
|
+
$id: RUN_REPORT_FORMAT,
|
|
13943
|
+
...info,
|
|
13944
|
+
iterations: report.iterations,
|
|
13945
|
+
ended: report.ended,
|
|
13946
|
+
...report.reason !== void 0 && { reason: report.reason },
|
|
13947
|
+
totals: counts(report.results),
|
|
13948
|
+
results: report.results
|
|
13949
|
+
};
|
|
12943
13950
|
}
|
|
12944
13951
|
function xml(text3) {
|
|
12945
|
-
return text3.replace(/[^\t\n\r --�\u{10000}-\u{10FFFF}]/gu, "\uFFFD").replace(
|
|
13952
|
+
return text3.replace(/[^\t\n\r --�\u{10000}-\u{10FFFF}]/gu, "\uFFFD").replace(
|
|
13953
|
+
/[&<>"\t\n\r]/g,
|
|
13954
|
+
(char) => ({ "&": "&", "<": "<", ">": ">", '"': """, " ": "	", "\n": " ", "\r": " " })[char] ?? char
|
|
13955
|
+
);
|
|
12946
13956
|
}
|
|
12947
13957
|
var seconds = (ms) => ((ms ?? 0) / 1e3).toFixed(3);
|
|
12948
13958
|
function junitXml(report, info) {
|
|
@@ -12953,12 +13963,18 @@ function junitXml(report, info) {
|
|
|
12953
13963
|
<failure type="AssertionFailure" message="${xml(test.error ?? "")}"/>
|
|
12954
13964
|
</testcase>`
|
|
12955
13965
|
);
|
|
12956
|
-
if (result.error !== void 0)
|
|
13966
|
+
if (result.error !== void 0)
|
|
13967
|
+
cases.push(
|
|
13968
|
+
` <testcase name="${classname}" classname="${classname}">
|
|
12957
13969
|
<error type="Error" message="${xml(result.error)}"/>
|
|
12958
|
-
</testcase>`
|
|
12959
|
-
|
|
13970
|
+
</testcase>`
|
|
13971
|
+
);
|
|
13972
|
+
if (result.skipped)
|
|
13973
|
+
cases.push(
|
|
13974
|
+
` <testcase name="${classname}" classname="${classname}">
|
|
12960
13975
|
<skipped message="pm.execution.skipRequest()"/>
|
|
12961
|
-
</testcase>`
|
|
13976
|
+
</testcase>`
|
|
13977
|
+
);
|
|
12962
13978
|
const totals2 = counts([result]);
|
|
12963
13979
|
const name = xml(report.iterations > 1 ? `${result.name} (iteration ${result.iteration + 1})` : result.name);
|
|
12964
13980
|
const open = ` <testsuite name="${name}" id="${xml(result.id)}" tests="${cases.length}" failures="${totals2.failedTests}" errors="${totals2.errors}" skipped="${totals2.skipped}" time="${seconds(result.durationMs)}"`;
|
|
@@ -12976,6 +13992,97 @@ ${cases.join("\n")}
|
|
|
12976
13992
|
""
|
|
12977
13993
|
].join("\n");
|
|
12978
13994
|
}
|
|
13995
|
+
var REPORT_COLOURS = {
|
|
13996
|
+
light: {
|
|
13997
|
+
canvas: "#FBFAF8",
|
|
13998
|
+
panel: "#F6F5F2",
|
|
13999
|
+
primary: "#26231E",
|
|
14000
|
+
secondary: "#55524D",
|
|
14001
|
+
tertiary: "#726F69",
|
|
14002
|
+
line: "#DEDAD3",
|
|
14003
|
+
error: "#A51F1E"
|
|
14004
|
+
},
|
|
14005
|
+
dark: {
|
|
14006
|
+
canvas: "#161513",
|
|
14007
|
+
panel: "#22211E",
|
|
14008
|
+
primary: "#EBE9E4",
|
|
14009
|
+
secondary: "#C0BDB6",
|
|
14010
|
+
tertiary: "#9A9790",
|
|
14011
|
+
line: "#423F39",
|
|
14012
|
+
error: "#F7958A"
|
|
14013
|
+
}
|
|
14014
|
+
};
|
|
14015
|
+
function html(text3) {
|
|
14016
|
+
return text3.replace(
|
|
14017
|
+
/[&<>"']/g,
|
|
14018
|
+
(char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char] ?? char
|
|
14019
|
+
);
|
|
14020
|
+
}
|
|
14021
|
+
var palette = (colours) => Object.entries(colours).map(([name, value]) => `--${name}: ${value};`).join(" ");
|
|
14022
|
+
function runReportHtml(report, info) {
|
|
14023
|
+
const shared = sharedErrors(report.results);
|
|
14024
|
+
const sharedNote = (error) => {
|
|
14025
|
+
const index = shared.findIndex((entry) => entry.error === error);
|
|
14026
|
+
return index < 0 ? error : shared.length === 1 ? "Same error as above" : `Same error as ${index + 1} above`;
|
|
14027
|
+
};
|
|
14028
|
+
const rows2 = report.results.map((result, index) => {
|
|
14029
|
+
const failed = result.tests.filter((test) => !test.passed);
|
|
14030
|
+
const problems = [
|
|
14031
|
+
...result.error === void 0 ? [] : [sharedNote(result.error)],
|
|
14032
|
+
...failed.map((test) => test.error ? `${test.name}: ${test.error}` : test.name)
|
|
14033
|
+
];
|
|
14034
|
+
const status = result.skipped ? "Skipped" : result.status ? `${result.status} ${result.statusText ?? ""}`.trim() : "No response";
|
|
14035
|
+
return `<tr${runFailed(result) ? ' class="failed"' : ""}>
|
|
14036
|
+
<td>${index + 1}</td><td>${result.iteration + 1}</td>
|
|
14037
|
+
<td>${html(result.name)}${problems.map((problem) => `<p class="problem">${html(problem)}</p>`).join("")}</td>
|
|
14038
|
+
<td>${html(status)}</td><td>${result.durationMs === void 0 ? "\u2014" : `${result.durationMs} ms`}</td>
|
|
14039
|
+
<td>${result.tests.length ? `${result.tests.length - failed.length} of ${result.tests.length} passed` : "\u2014"}</td>
|
|
14040
|
+
</tr>`;
|
|
14041
|
+
}).join("\n");
|
|
14042
|
+
const sharedBlocks = shared.map(
|
|
14043
|
+
({ error, count }, index) => `<p class="shared">${shared.length > 1 ? `<b>${index + 1}.</b> ` : ""}<b>${html(sharedErrorHeading(count))}</b> ${html(error)}</p>`
|
|
14044
|
+
).join("\n");
|
|
14045
|
+
return `<!doctype html>
|
|
14046
|
+
<html lang="en-GB">
|
|
14047
|
+
<head>
|
|
14048
|
+
<meta charset="utf-8">
|
|
14049
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
14050
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'">
|
|
14051
|
+
<title>Run ${html(info.name)}</title>
|
|
14052
|
+
<style>
|
|
14053
|
+
:root { color-scheme: light dark; ${palette(REPORT_COLOURS.light)} }
|
|
14054
|
+
@media (prefers-color-scheme: dark) { :root { ${palette(REPORT_COLOURS.dark)} } }
|
|
14055
|
+
body { margin: 0; padding: 24px 16px; background: var(--canvas); color: var(--primary);
|
|
14056
|
+
font: 14px/1.5 "Atkinson Hyperlegible Next", system-ui, sans-serif; }
|
|
14057
|
+
main { max-width: 1100px; margin: 0 auto; }
|
|
14058
|
+
h1 { margin: 0 0 4px; font-size: 1.375rem; font-weight: 600; }
|
|
14059
|
+
.meta, .counts { margin: 0 0 16px; color: var(--secondary); }
|
|
14060
|
+
.shared { margin: 0 0 12px; padding: 10px 14px; border: 1px solid var(--error); border-radius: 8px; color: var(--error); }
|
|
14061
|
+
table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }
|
|
14062
|
+
th { text-align: start; font-weight: 500; color: var(--tertiary); }
|
|
14063
|
+
th, td { padding: 8px 10px; border-bottom: 1px solid var(--line); vertical-align: top; overflow-wrap: anywhere; }
|
|
14064
|
+
td:nth-child(4), td:nth-child(5) { font-family: "JetBrains Mono", ui-monospace, monospace; }
|
|
14065
|
+
.problem { margin: 4px 0 0; color: var(--error); font-size: 0.75rem; }
|
|
14066
|
+
tr.failed td:first-child { box-shadow: inset 2px 0 0 var(--error); }
|
|
14067
|
+
</style>
|
|
14068
|
+
</head>
|
|
14069
|
+
<body>
|
|
14070
|
+
<main>
|
|
14071
|
+
<h1>Run ${html(info.name)}</h1>
|
|
14072
|
+
<p class="meta">Started ${html(info.startedAt)}</p>
|
|
14073
|
+
<p class="counts">${html(runEnding(report))} ${html(runCounts(report))}</p>
|
|
14074
|
+
${sharedBlocks}
|
|
14075
|
+
<table>
|
|
14076
|
+
<thead><tr><th scope="col">#</th><th scope="col">Iteration</th><th scope="col">Request</th><th scope="col">Status</th><th scope="col">Time</th><th scope="col">Tests</th></tr></thead>
|
|
14077
|
+
<tbody>
|
|
14078
|
+
${rows2}
|
|
14079
|
+
</tbody>
|
|
14080
|
+
</table>
|
|
14081
|
+
</main>
|
|
14082
|
+
</body>
|
|
14083
|
+
</html>
|
|
14084
|
+
`;
|
|
14085
|
+
}
|
|
12979
14086
|
|
|
12980
14087
|
// src/run.ts
|
|
12981
14088
|
import variant from "@jitl/quickjs-wasmfile-release-sync";
|
|
@@ -13020,10 +14127,16 @@ async function runCollection(input, options) {
|
|
|
13020
14127
|
const transport = new FetchTransport();
|
|
13021
14128
|
const ports = {
|
|
13022
14129
|
runScript: async (script, env, response, { send: send2, ...extras }) => {
|
|
13023
|
-
const result = await runScript(
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
14130
|
+
const result = await runScript(
|
|
14131
|
+
await engine(),
|
|
14132
|
+
script,
|
|
14133
|
+
{ ...extras, environment: env, ...response && { response: scriptResponse(response) } },
|
|
14134
|
+
SCRIPT_LIMITS,
|
|
14135
|
+
{
|
|
14136
|
+
libraries: await loadLibraries(script, loadLibrary, libraries),
|
|
14137
|
+
...send2 && { send: send2 }
|
|
14138
|
+
}
|
|
14139
|
+
);
|
|
13027
14140
|
if (result.limit) engine = engines();
|
|
13028
14141
|
return result;
|
|
13029
14142
|
},
|
|
@@ -13038,7 +14151,9 @@ async function runCollection(input, options) {
|
|
|
13038
14151
|
environment: () => environment2 && {
|
|
13039
14152
|
name: environment2.name,
|
|
13040
14153
|
vars: environment2.vars,
|
|
13041
|
-
change: (changes) => Promise.resolve(
|
|
14154
|
+
change: (changes) => Promise.resolve(
|
|
14155
|
+
environment2 && (environment2 = { ...environment2, vars: applyVariableChanges(environment2.vars, changes) })
|
|
14156
|
+
)
|
|
13042
14157
|
},
|
|
13043
14158
|
globals: () => globals,
|
|
13044
14159
|
changeGlobals: (changes) => Promise.resolve(globals = applyVariableChanges(globals, changes)),
|
|
@@ -13060,7 +14175,13 @@ async function runCollection(input, options) {
|
|
|
13060
14175
|
const send = async (doc, iteration, signal) => {
|
|
13061
14176
|
const ancestors = ancestorsOf(source.collection, source.folders, doc.folderId);
|
|
13062
14177
|
const folder = folderChain(source.folders, doc.folderId).flatMap((entry) => entry.variables);
|
|
13063
|
-
const scopes = () => ({
|
|
14178
|
+
const scopes = () => ({
|
|
14179
|
+
environment: environment2?.vars,
|
|
14180
|
+
folder,
|
|
14181
|
+
collection: collectionVariables,
|
|
14182
|
+
global: globals,
|
|
14183
|
+
data: iteration.data
|
|
14184
|
+
});
|
|
13064
14185
|
const { outcome } = await runOutcome(
|
|
13065
14186
|
() => exchange(
|
|
13066
14187
|
{
|
|
@@ -13073,7 +14194,10 @@ async function runCollection(input, options) {
|
|
|
13073
14194
|
scopes,
|
|
13074
14195
|
ancestors,
|
|
13075
14196
|
readSecrets: async (ids) => {
|
|
13076
|
-
if (!vault)
|
|
14197
|
+
if (!vault)
|
|
14198
|
+
throw new SendBlockedError(
|
|
14199
|
+
"This request uses a vault secret, and the run has no vault: give --vault-file, or run a workspace backup, with the passphrase in WAYFARER_VAULT_PASSPHRASE. Nothing was sent."
|
|
14200
|
+
);
|
|
13077
14201
|
const found = /* @__PURE__ */ new Map();
|
|
13078
14202
|
for (const id of ids) {
|
|
13079
14203
|
const value = await readSecret(id);
|
|
@@ -13081,12 +14205,27 @@ async function runCollection(input, options) {
|
|
|
13081
14205
|
}
|
|
13082
14206
|
return found;
|
|
13083
14207
|
},
|
|
13084
|
-
readFile: (ref) => Promise.reject(
|
|
14208
|
+
readFile: (ref) => Promise.reject(
|
|
14209
|
+
new SendBlockedError(
|
|
14210
|
+
`The file "${ref.fileName}" of this request's body is kept in the browser that picked it, not in the file run here. Nothing was sent.`
|
|
14211
|
+
)
|
|
14212
|
+
),
|
|
13085
14213
|
blockUnresolved: true,
|
|
13086
|
-
composed: {
|
|
14214
|
+
composed: {
|
|
14215
|
+
url: doc.url,
|
|
14216
|
+
scopes: { ...baseline, folder, data: iteration.data },
|
|
14217
|
+
accepted: acceptedHosts
|
|
14218
|
+
}
|
|
13087
14219
|
}),
|
|
13088
|
-
info: {
|
|
13089
|
-
|
|
14220
|
+
info: {
|
|
14221
|
+
requestName: doc.name,
|
|
14222
|
+
requestId: doc.id,
|
|
14223
|
+
path: [source.collection.name, ...folderChain(source.folders, doc.folderId).map((entry) => entry.name)]
|
|
14224
|
+
},
|
|
14225
|
+
collection: {
|
|
14226
|
+
variables: () => collectionVariables,
|
|
14227
|
+
change: (changes) => Promise.resolve(collectionVariables = applyVariableChanges(collectionVariables, changes))
|
|
14228
|
+
},
|
|
13090
14229
|
inherited: ancestors,
|
|
13091
14230
|
folderVariables: () => folder,
|
|
13092
14231
|
signal,
|
|
@@ -13127,9 +14266,10 @@ Options:
|
|
|
13127
14266
|
-n, --iterations <n> How many iterations (default: one per row of the
|
|
13128
14267
|
data file, or 1).
|
|
13129
14268
|
--collection <name> Which collection of a file that holds several.
|
|
13130
|
-
--reporter <list> cli, json, junit, comma-separated (default
|
|
13131
|
-
json writes wayfarer-run.json
|
|
13132
|
-
wayfarer-run.junit.xml
|
|
14269
|
+
--reporter <list> cli, json, junit, html, comma-separated (default
|
|
14270
|
+
cli). json writes wayfarer-run.json, junit
|
|
14271
|
+
wayfarer-run.junit.xml and html wayfarer-run.html,
|
|
14272
|
+
in --out.
|
|
13133
14273
|
--out <dir> Where the reports are written (default: here).
|
|
13134
14274
|
--bail Stop at the first failure.
|
|
13135
14275
|
--timeout <ms> Give up on a request after this long (default: no
|
|
@@ -13148,46 +14288,64 @@ Exit codes: 0 every request answered and every test passed; 1 a test failed
|
|
|
13148
14288
|
or a request got no response; 2 nothing was run (see the message).`;
|
|
13149
14289
|
var Refusal = class extends Error {
|
|
13150
14290
|
};
|
|
13151
|
-
async function
|
|
14291
|
+
async function readText2(file, what) {
|
|
13152
14292
|
try {
|
|
13153
14293
|
return await readFile(file, "utf8");
|
|
13154
14294
|
} catch (error) {
|
|
13155
|
-
throw new Refusal(
|
|
14295
|
+
throw new Refusal(
|
|
14296
|
+
`The ${what} ${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
14297
|
+
);
|
|
13156
14298
|
}
|
|
13157
14299
|
}
|
|
13158
14300
|
function pick(list4, name, what, file) {
|
|
13159
14301
|
if (name !== void 0) {
|
|
13160
14302
|
const found = list4.find((entry) => entry.name === name);
|
|
13161
|
-
if (!found)
|
|
14303
|
+
if (!found)
|
|
14304
|
+
throw new Refusal(
|
|
14305
|
+
`${file} has no ${what} named "${name}". It has: ${list4.map((entry) => `"${entry.name}"`).join(", ")}.`
|
|
14306
|
+
);
|
|
13162
14307
|
return found;
|
|
13163
14308
|
}
|
|
13164
|
-
if (list4.length > 1)
|
|
14309
|
+
if (list4.length > 1)
|
|
14310
|
+
throw new Refusal(
|
|
14311
|
+
`${file} holds ${list4.length} ${what}s: name one with --${what} (${list4.map((entry) => `"${entry.name}"`).join(", ")}).`
|
|
14312
|
+
);
|
|
13165
14313
|
const [only] = list4;
|
|
13166
14314
|
if (!only) throw new Refusal(`${file} holds no ${what}.`);
|
|
13167
14315
|
return only;
|
|
13168
14316
|
}
|
|
13169
14317
|
async function openVault(record, secrets, file) {
|
|
13170
14318
|
const passphrase = process.env["WAYFARER_VAULT_PASSPHRASE"];
|
|
13171
|
-
if (passphrase === void 0)
|
|
14319
|
+
if (passphrase === void 0)
|
|
14320
|
+
throw new Refusal(`${file} holds a vault: set WAYFARER_VAULT_PASSPHRASE to its passphrase.`);
|
|
13172
14321
|
const dek = await unwrapDek(record, passphrase);
|
|
13173
14322
|
if (!dek) throw new Refusal(`WAYFARER_VAULT_PASSPHRASE is not the passphrase of the vault in ${file}.`);
|
|
13174
14323
|
return { dek, secrets: new Map(secrets.map((secret) => [secret.id, secret.envelope])) };
|
|
13175
14324
|
}
|
|
13176
14325
|
async function readSource(file, collection) {
|
|
13177
|
-
const text3 = await
|
|
14326
|
+
const text3 = await readText2(file, "file");
|
|
13178
14327
|
const parsed2 = parseJson(text3);
|
|
13179
14328
|
const id = parsed2.ok && typeof parsed2.value === "object" && parsed2.value !== null && "$id" in parsed2.value ? String(parsed2.value.$id) : "";
|
|
13180
14329
|
if (id !== "wayfarer/workspace" && !id.startsWith("wayfarer/workspace/")) {
|
|
13181
14330
|
try {
|
|
13182
14331
|
const { collections } = importText(text3);
|
|
13183
|
-
return {
|
|
14332
|
+
return {
|
|
14333
|
+
source: pick(
|
|
14334
|
+
collections.map(({ payload }) => ({ name: payload.collection.name, payload })),
|
|
14335
|
+
collection,
|
|
14336
|
+
"collection",
|
|
14337
|
+
file
|
|
14338
|
+
).payload,
|
|
14339
|
+
globals: []
|
|
14340
|
+
};
|
|
13184
14341
|
} catch (error) {
|
|
13185
14342
|
if (error instanceof Error && error.name === "ImportError") throw new Refusal(`${file}: ${error.message}`);
|
|
13186
14343
|
throw error;
|
|
13187
14344
|
}
|
|
13188
14345
|
}
|
|
13189
14346
|
const errors = validateWorkspaceFile(parsed2.ok ? parsed2.value : void 0);
|
|
13190
|
-
if (errors.length)
|
|
14347
|
+
if (errors.length)
|
|
14348
|
+
throw new Refusal(`${file} is not a workspace backup this version reads:
|
|
13191
14349
|
${errors.join("\n ")}`);
|
|
13192
14350
|
const { stores } = parsed2.ok ? parsed2.value : {};
|
|
13193
14351
|
const chosen = pick(stores.collections, collection, "collection", file);
|
|
@@ -13207,12 +14365,14 @@ async function readSource(file, collection) {
|
|
|
13207
14365
|
...environment2 && { environment: { name: environment2.name, vars: environment2.vars } },
|
|
13208
14366
|
globals: meta(META_GLOBALS_KEY)?.["variables"] ?? [],
|
|
13209
14367
|
// A backup's secrets are useful only with the passphrase: without one, a request that needs a secret says so.
|
|
13210
|
-
...vault && process.env["WAYFARER_VAULT_PASSPHRASE"] !== void 0 && {
|
|
14368
|
+
...vault && process.env["WAYFARER_VAULT_PASSPHRASE"] !== void 0 && {
|
|
14369
|
+
vault: await openVault(vault, stores.secrets, file)
|
|
14370
|
+
}
|
|
13211
14371
|
};
|
|
13212
14372
|
}
|
|
13213
14373
|
async function readEnvironment(file) {
|
|
13214
14374
|
try {
|
|
13215
|
-
const { environments } = importText(await
|
|
14375
|
+
const { environments } = importText(await readText2(file, "environment"));
|
|
13216
14376
|
const { name, vars } = pick(environments, void 0, "environment", file);
|
|
13217
14377
|
return { name, vars };
|
|
13218
14378
|
} catch (error) {
|
|
@@ -13221,9 +14381,12 @@ async function readEnvironment(file) {
|
|
|
13221
14381
|
}
|
|
13222
14382
|
}
|
|
13223
14383
|
async function readVaultFile(file) {
|
|
13224
|
-
const parsed2 = parseJson(await
|
|
14384
|
+
const parsed2 = parseJson(await readText2(file, "vault file"));
|
|
13225
14385
|
const issues = parsed2.ok ? validateVaultFile(parsed2.value) : [{ path: "", message: "not JSON" }];
|
|
13226
|
-
if (issues.length)
|
|
14386
|
+
if (issues.length)
|
|
14387
|
+
throw new Refusal(
|
|
14388
|
+
`${file} is not a vault file ("$id": "${VAULT_FILE_FORMAT}"): ${issues.map((issue) => `${issue.path} ${issue.message}`.trim()).join("; ")}`
|
|
14389
|
+
);
|
|
13227
14390
|
const { vault, secrets } = parsed2.ok ? parsed2.value : {};
|
|
13228
14391
|
return openVault(vault, secrets, file);
|
|
13229
14392
|
}
|
|
@@ -13234,21 +14397,22 @@ function scriptHolders(source) {
|
|
|
13234
14397
|
...source.requests.filter((request) => scriptsOf(request).length).map((request) => `the request "${request.name}"`)
|
|
13235
14398
|
];
|
|
13236
14399
|
}
|
|
13237
|
-
function line(result, iterations) {
|
|
14400
|
+
function line(result, iterations, said2) {
|
|
13238
14401
|
const mark = runFailed(result) ? "\u2717" : result.skipped ? "-" : "\u2713";
|
|
13239
14402
|
const where = iterations > 1 ? ` (iteration ${result.iteration + 1})` : "";
|
|
13240
14403
|
const status = result.skipped ? "skipped" : result.status ? `${result.status} ${result.statusText ?? ""}`.trim() : "no response";
|
|
13241
14404
|
const tests2 = result.tests.map((test) => `
|
|
13242
14405
|
${test.passed ? "\u2713" : "\u2717"} ${test.name}${test.error ? `: ${test.error}` : ""}`).join("");
|
|
13243
|
-
const
|
|
13244
|
-
|
|
14406
|
+
const repeated = result.error !== void 0 && said2.has(result.error);
|
|
14407
|
+
if (result.error !== void 0) said2.add(result.error);
|
|
14408
|
+
const error = result.error === void 0 ? "" : `
|
|
14409
|
+
${repeated ? "Same error as before." : result.error}`;
|
|
13245
14410
|
return `${mark} ${result.name}${where}: ${status}${result.durationMs !== void 0 ? `, ${result.durationMs} ms` : ""}${tests2}${error}`;
|
|
13246
14411
|
}
|
|
13247
14412
|
function summary(report) {
|
|
13248
|
-
const
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
return `${report.results.length} results in ${report.iterations} iteration${report.iterations === 1 ? "" : "s"}; ${failed} failed; ${tests2.filter((test) => test.passed).length} of ${tests2.length} tests passed.${ended}`;
|
|
14413
|
+
const shared = sharedErrors(report.results).map(({ error, count }) => `
|
|
14414
|
+
${sharedErrorHeading(count)} ${error}`);
|
|
14415
|
+
return `${runEnding(report)} ${runCounts(report)}${shared.join("")}`;
|
|
13252
14416
|
}
|
|
13253
14417
|
async function main(argv) {
|
|
13254
14418
|
const { values, positionals } = parseArgs({
|
|
@@ -13271,7 +14435,7 @@ async function main(argv) {
|
|
|
13271
14435
|
}
|
|
13272
14436
|
});
|
|
13273
14437
|
if (values.version) {
|
|
13274
|
-
console.log("2.0
|
|
14438
|
+
console.log("2.1.0");
|
|
13275
14439
|
return 0;
|
|
13276
14440
|
}
|
|
13277
14441
|
const [command, file, ...rest] = positionals;
|
|
@@ -13279,11 +14443,14 @@ async function main(argv) {
|
|
|
13279
14443
|
console.log(USAGE);
|
|
13280
14444
|
return values.help ? 0 : 2;
|
|
13281
14445
|
}
|
|
13282
|
-
if (command !== "run" || file === void 0 || rest.length)
|
|
14446
|
+
if (command !== "run" || file === void 0 || rest.length)
|
|
14447
|
+
throw new Refusal(`Unknown command or arguments: ${positionals.join(" ")}
|
|
13283
14448
|
|
|
13284
14449
|
${USAGE}`);
|
|
13285
14450
|
const reporters = new Set(values.reporter.split(",").map((name) => name.trim()));
|
|
13286
|
-
for (const name of reporters)
|
|
14451
|
+
for (const name of reporters)
|
|
14452
|
+
if (!["cli", "json", "junit", "html"].includes(name))
|
|
14453
|
+
throw new Refusal(`Unknown reporter "${name}": choose from cli, json, junit and html.`);
|
|
13287
14454
|
const number = (text3, flag2) => {
|
|
13288
14455
|
if (text3 === void 0) return void 0;
|
|
13289
14456
|
if (!/^\d+$/.test(text3)) throw new Refusal(`--${flag2} takes a whole number, not "${text3}".`);
|
|
@@ -13295,43 +14462,56 @@ ${USAGE}`);
|
|
|
13295
14462
|
const read = await readSource(file, values.collection);
|
|
13296
14463
|
const holders = scriptHolders(read.source);
|
|
13297
14464
|
if (holders.length && !values["allow-scripts"]) {
|
|
13298
|
-
throw new Refusal(
|
|
14465
|
+
throw new Refusal(
|
|
14466
|
+
`This collection has scripts, in ${holders.join(", ")}. A file's scripts have not been reviewed: read them, and run again with --allow-scripts only if you trust them. Nothing was sent.`
|
|
14467
|
+
);
|
|
13299
14468
|
}
|
|
13300
14469
|
const environment2 = values.environment === void 0 ? read.environment : await readEnvironment(values.environment);
|
|
13301
14470
|
const vault = values["vault-file"] === void 0 ? read.vault : await readVaultFile(values["vault-file"]);
|
|
13302
14471
|
let data;
|
|
13303
14472
|
if (values.data !== void 0) {
|
|
13304
14473
|
try {
|
|
13305
|
-
data = parseDataFile(await
|
|
14474
|
+
data = parseDataFile(await readText2(values.data, "data file"), basename(values.data));
|
|
13306
14475
|
} catch (error) {
|
|
13307
|
-
if (error instanceof Error && error.name === "DataFileError")
|
|
14476
|
+
if (error instanceof Error && error.name === "DataFileError")
|
|
14477
|
+
throw new Refusal(`${values.data}: ${error.message}`);
|
|
13308
14478
|
throw error;
|
|
13309
14479
|
}
|
|
13310
14480
|
}
|
|
13311
14481
|
if (values.insecure) process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";
|
|
13312
14482
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13313
|
-
if (reporters.has("json") || reporters.has("junit")) {
|
|
14483
|
+
if (reporters.has("json") || reporters.has("junit") || reporters.has("html")) {
|
|
13314
14484
|
await mkdir(values.out, { recursive: true }).catch((error) => {
|
|
13315
14485
|
throw new Refusal(`--out ${values.out}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13316
14486
|
});
|
|
13317
14487
|
}
|
|
14488
|
+
const said2 = /* @__PURE__ */ new Set();
|
|
13318
14489
|
const report = await runCollection(
|
|
13319
|
-
{
|
|
14490
|
+
{
|
|
14491
|
+
source: read.source,
|
|
14492
|
+
...environment2 && { environment: environment2 },
|
|
14493
|
+
globals: read.globals,
|
|
14494
|
+
...vault && { vault },
|
|
14495
|
+
allowScripts: values["allow-scripts"],
|
|
14496
|
+
timeoutMs
|
|
14497
|
+
},
|
|
13320
14498
|
{
|
|
13321
14499
|
...iterations !== void 0 && { iterations },
|
|
13322
14500
|
...data && { data },
|
|
13323
14501
|
stopOnFailure: values.bail,
|
|
13324
14502
|
onResult: (result) => {
|
|
13325
|
-
if (reporters.has("cli")) console.log(line(result, iterations ?? data?.length ?? 1));
|
|
14503
|
+
if (reporters.has("cli")) console.log(line(result, iterations ?? data?.length ?? 1, said2));
|
|
13326
14504
|
}
|
|
13327
14505
|
}
|
|
13328
14506
|
);
|
|
13329
14507
|
const info = { name: read.source.collection.name, startedAt };
|
|
13330
14508
|
if (reporters.has("cli")) console.log(`
|
|
13331
14509
|
${summary(report)}`);
|
|
13332
|
-
if (reporters.has("json"))
|
|
14510
|
+
if (reporters.has("json"))
|
|
14511
|
+
await writeFile(join(values.out, "wayfarer-run.json"), `${JSON.stringify(runReportJson(report, info), null, 2)}
|
|
13333
14512
|
`);
|
|
13334
14513
|
if (reporters.has("junit")) await writeFile(join(values.out, "wayfarer-run.junit.xml"), junitXml(report, info));
|
|
14514
|
+
if (reporters.has("html")) await writeFile(join(values.out, "wayfarer-run.html"), runReportHtml(report, info));
|
|
13335
14515
|
return report.results.some(runFailed) ? 1 : 0;
|
|
13336
14516
|
}
|
|
13337
14517
|
main(process.argv.slice(2)).then(
|