wayfarer-cli 2.0.1 → 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 +1522 -336
- 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,9 +8928,13 @@ 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
|
-
|
|
8721
|
-
|
|
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.");
|
|
8937
|
+
throw new TransportError("network", networkMessage(cause));
|
|
8722
8938
|
} finally {
|
|
8723
8939
|
clearTimeout(timer);
|
|
8724
8940
|
options.signal.removeEventListener("abort", cancel);
|
|
@@ -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;
|
|
@@ -8776,6 +8993,11 @@ async function readEventStream(response, signal, onEvent) {
|
|
|
8776
8993
|
}
|
|
8777
8994
|
return { body: kept.slice(0, size).buffer, stream: { stopped, ...truncated && { truncated } } };
|
|
8778
8995
|
}
|
|
8996
|
+
function networkMessage(cause) {
|
|
8997
|
+
if (!(cause instanceof Error)) return String(cause);
|
|
8998
|
+
const code = cause.cause instanceof Error && "code" in cause.cause ? cause.cause.code : void 0;
|
|
8999
|
+
return typeof code === "string" && /^[A-Z][A-Z0-9_]*$/.test(code) ? `${cause.message} (${code})` : cause.message;
|
|
9000
|
+
}
|
|
8779
9001
|
|
|
8780
9002
|
// ../core/src/transport/fetch.ts
|
|
8781
9003
|
var FetchTransport = class {
|
|
@@ -9969,7 +10191,8 @@ var isVmHash = (name) => Object.hasOwn(HASHES, name);
|
|
|
9969
10191
|
var VM_ENCODINGS = ["utf8", "hex", "base64"];
|
|
9970
10192
|
function fromWords(json) {
|
|
9971
10193
|
const list4 = JSON.parse(json);
|
|
9972
|
-
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.");
|
|
9973
10196
|
const [length, ...words] = list4;
|
|
9974
10197
|
const bytes = new Uint8Array(Math.max(0, Math.min(length, words.length * 4)));
|
|
9975
10198
|
for (let i = 0; i < bytes.length; i++) bytes[i] = words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
|
|
@@ -9993,7 +10216,10 @@ function vmHmac(name, keyFormat, key, format, payload) {
|
|
|
9993
10216
|
}
|
|
9994
10217
|
function vmEncode(encoding, text3) {
|
|
9995
10218
|
if (encoding === "utf8") return toWords(new TextEncoder().encode(text3));
|
|
9996
|
-
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
|
+
);
|
|
9997
10223
|
return toWords(Uint8Array.from(atob(text3.replace(/=+$/, "")), (char) => char.charCodeAt(0)));
|
|
9998
10224
|
}
|
|
9999
10225
|
function vmDecode(encoding, words) {
|
|
@@ -10004,12 +10230,14 @@ function vmDecode(encoding, words) {
|
|
|
10004
10230
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
10005
10231
|
} catch (error) {
|
|
10006
10232
|
if (!(error instanceof TypeError)) throw error;
|
|
10007
|
-
throw new Error("Malformed UTF-8 data");
|
|
10233
|
+
throw new Error("Malformed UTF-8 data", { cause: error });
|
|
10008
10234
|
}
|
|
10009
10235
|
}
|
|
10010
10236
|
function vmRandom(count) {
|
|
10011
10237
|
if (!Number.isInteger(count) || count < 0 || count > 65536) throw new RangeError("Expected up to 65536 bytes.");
|
|
10012
|
-
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
|
+
);
|
|
10013
10241
|
}
|
|
10014
10242
|
|
|
10015
10243
|
// ../core/src/scripting/host.ts
|
|
@@ -10034,7 +10262,10 @@ var SCRIPT_LIMITS = {
|
|
|
10034
10262
|
var WASM_PAGE = 65536;
|
|
10035
10263
|
var ENGINE_INITIAL_PAGES = 256;
|
|
10036
10264
|
function scriptMemory(limits = SCRIPT_LIMITS) {
|
|
10037
|
-
return {
|
|
10265
|
+
return {
|
|
10266
|
+
initial: ENGINE_INITIAL_PAGES,
|
|
10267
|
+
maximum: Math.max(ENGINE_INITIAL_PAGES, Math.floor(limits.memoryBytes / WASM_PAGE))
|
|
10268
|
+
};
|
|
10038
10269
|
}
|
|
10039
10270
|
var LOG_TRUNCATED = "[console output truncated]";
|
|
10040
10271
|
function emptyChanges() {
|
|
@@ -10049,9 +10280,30 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
|
|
|
10049
10280
|
const scope = (rows2 = []) => new Map(rows2.map(([name, value]) => [name, value]));
|
|
10050
10281
|
const marked = (rows2 = []) => new Set(rows2.filter(([, value, json]) => json === true && isJsonText(value)).map(([name]) => name));
|
|
10051
10282
|
const state = {
|
|
10052
|
-
values: {
|
|
10053
|
-
|
|
10054
|
-
|
|
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
|
+
},
|
|
10055
10307
|
sends: 0,
|
|
10056
10308
|
inFlight: 0,
|
|
10057
10309
|
answers: [],
|
|
@@ -10061,7 +10313,11 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
|
|
|
10061
10313
|
tests: [],
|
|
10062
10314
|
timers: /* @__PURE__ */ new Map()
|
|
10063
10315
|
};
|
|
10064
|
-
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
|
+
}));
|
|
10065
10321
|
const result = (error, limit) => ({
|
|
10066
10322
|
logs: state.logs,
|
|
10067
10323
|
changes: { environment: changed("environment"), collection: changed("collection"), global: changed("global") },
|
|
@@ -10081,7 +10337,8 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
|
|
|
10081
10337
|
try {
|
|
10082
10338
|
failure = await evaluate(vm, runtime, source, context, state, deadline, limits, services);
|
|
10083
10339
|
} catch (error) {
|
|
10084
|
-
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");
|
|
10085
10342
|
throw error;
|
|
10086
10343
|
}
|
|
10087
10344
|
vm.dispose();
|
|
@@ -10125,7 +10382,8 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10125
10382
|
};
|
|
10126
10383
|
const charge = (...written) => {
|
|
10127
10384
|
state.written += written.reduce((sum, part) => sum + part.length, 0);
|
|
10128
|
-
if (state.written > limits.maxOutputBytes)
|
|
10385
|
+
if (state.written > limits.maxOutputBytes)
|
|
10386
|
+
throw new RangeError("Script wrote too many test results and variables.");
|
|
10129
10387
|
};
|
|
10130
10388
|
const scopeOf = (handle) => {
|
|
10131
10389
|
const name = text3(handle);
|
|
@@ -10148,7 +10406,9 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10148
10406
|
});
|
|
10149
10407
|
bind("varHas", (where, key) => lookUp(where, text3(key)) === void 0 ? vm.false : vm.true);
|
|
10150
10408
|
bind("varAll", (where) => {
|
|
10151
|
-
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
|
+
);
|
|
10152
10412
|
const entries = any(where) ? [...VARIABLE_ORDER].reverse().flatMap(of2) : of2(scopeOf(where));
|
|
10153
10413
|
return vm.newString(`[${entries.join(",")}]`);
|
|
10154
10414
|
});
|
|
@@ -10214,7 +10474,8 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10214
10474
|
const id = text3(name);
|
|
10215
10475
|
const source2 = services.libraries?.get(id);
|
|
10216
10476
|
if (source2 !== void 0) return vm.newString(source2);
|
|
10217
|
-
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}')`);
|
|
10218
10479
|
return void 0;
|
|
10219
10480
|
});
|
|
10220
10481
|
const hash = (handle) => {
|
|
@@ -10229,7 +10490,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10229
10490
|
return found;
|
|
10230
10491
|
};
|
|
10231
10492
|
bind("hash", (name, format, payload) => vm.newString(vmHash(hash(name), text3(format), text3(payload))));
|
|
10232
|
-
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
|
+
);
|
|
10233
10497
|
bind("encode", (name, value) => vm.newString(vmEncode(encoding(name), text3(value))));
|
|
10234
10498
|
bind("decode", (name, words) => vm.newString(vmDecode(encoding(name), text3(words))));
|
|
10235
10499
|
bind("random", (count) => vm.newString(vmRandom(number(count))));
|
|
@@ -10253,7 +10517,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10253
10517
|
state.answers.push([call, ok, JSON.stringify(value)]);
|
|
10254
10518
|
state.wake?.();
|
|
10255
10519
|
};
|
|
10256
|
-
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
|
+
});
|
|
10257
10524
|
bind("sendFailed", (call, name, message) => {
|
|
10258
10525
|
answer(number(call), false, { name: text3(name), message: text3(message) });
|
|
10259
10526
|
return void 0;
|
|
@@ -10262,7 +10529,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10262
10529
|
const [id, body3] = [number(call), text3(json)];
|
|
10263
10530
|
state.sends++;
|
|
10264
10531
|
if (state.sends > limits.maxSendRequests) {
|
|
10265
|
-
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
|
+
});
|
|
10266
10536
|
return void 0;
|
|
10267
10537
|
}
|
|
10268
10538
|
const send = services.send;
|
|
@@ -10365,12 +10635,15 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10365
10635
|
}
|
|
10366
10636
|
}
|
|
10367
10637
|
var VARIABLE_ORDER = VARIABLE_SCOPES;
|
|
10368
|
-
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
|
+
);
|
|
10369
10641
|
function parsed(json) {
|
|
10370
10642
|
const value = JSON.parse(json);
|
|
10371
10643
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Expected a request.");
|
|
10372
10644
|
const request = value;
|
|
10373
|
-
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.");
|
|
10374
10647
|
if (typeof request["url"] !== "string" || !request["url"].trim()) throw new TypeError("The request has no address.");
|
|
10375
10648
|
if (!isPairs(request["headers"])) throw new TypeError("The request's headers are not a list of names and values.");
|
|
10376
10649
|
return request;
|
|
@@ -10379,15 +10652,26 @@ function sendRequest(json) {
|
|
|
10379
10652
|
const request = parsed(json);
|
|
10380
10653
|
const body3 = request["body"];
|
|
10381
10654
|
if (body3 !== void 0 && typeof body3 !== "string") throw new TypeError("The request's body is not text.");
|
|
10382
|
-
return {
|
|
10655
|
+
return {
|
|
10656
|
+
method: request["method"],
|
|
10657
|
+
url: request["url"],
|
|
10658
|
+
headers: request["headers"],
|
|
10659
|
+
...body3 !== void 0 && { body: body3 }
|
|
10660
|
+
};
|
|
10383
10661
|
}
|
|
10384
10662
|
function scriptRequest(json) {
|
|
10385
10663
|
const request = parsed(json);
|
|
10386
10664
|
const body3 = request["body"];
|
|
10387
|
-
const base = {
|
|
10665
|
+
const base = {
|
|
10666
|
+
method: request["method"],
|
|
10667
|
+
url: request["url"],
|
|
10668
|
+
headers: request["headers"]
|
|
10669
|
+
};
|
|
10388
10670
|
if (body3?.mode === "raw" && typeof body3.raw === "string") return { ...base, body: { mode: "raw", raw: body3.raw } };
|
|
10389
|
-
if (body3?.mode === "urlencoded" && isPairs(body3.urlencoded))
|
|
10390
|
-
|
|
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 } };
|
|
10391
10675
|
throw new TypeError("The request's body is not one a script can set.");
|
|
10392
10676
|
}
|
|
10393
10677
|
function property(vm, object, name) {
|
|
@@ -10409,7 +10693,14 @@ function scriptRequestOf(content) {
|
|
|
10409
10693
|
function withScriptRequest(content, request) {
|
|
10410
10694
|
const rows2 = (pairs2) => pairs2.map(([key, value]) => ({ key, value, enabled: true }));
|
|
10411
10695
|
const untouched = content.body.mode === "graphql" && request.body.mode === "raw" && request.body.raw === graphqlText(content.body.graphql?.query ?? "", content.body.graphql?.variables ?? "");
|
|
10412
|
-
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;
|
|
10413
10704
|
return { ...content, method: request.method, url: request.url, headers: rows2(request.headers), body: body3 };
|
|
10414
10705
|
}
|
|
10415
10706
|
function sentScriptRequest(content, sent) {
|
|
@@ -10452,7 +10743,12 @@ async function runRequests(requests, send, options = {}) {
|
|
|
10452
10743
|
const count = options.iterations ?? (data.length || 1);
|
|
10453
10744
|
const signal = options.signal ?? new AbortController().signal;
|
|
10454
10745
|
const results = [];
|
|
10455
|
-
const report = (ended, reason) => ({
|
|
10746
|
+
const report = (ended, reason) => ({
|
|
10747
|
+
iterations: count,
|
|
10748
|
+
results,
|
|
10749
|
+
ended,
|
|
10750
|
+
...reason !== void 0 && { reason }
|
|
10751
|
+
});
|
|
10456
10752
|
let first = true;
|
|
10457
10753
|
for (let index = 0; index < count; index++) {
|
|
10458
10754
|
const iteration = { index, count, data: data[Math.min(index, data.length - 1)] ?? [] };
|
|
@@ -10474,6 +10770,35 @@ async function runRequests(requests, send, options = {}) {
|
|
|
10474
10770
|
return report("completed");
|
|
10475
10771
|
}
|
|
10476
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
|
+
|
|
10477
10802
|
// ../core/src/send/wire.ts
|
|
10478
10803
|
var SECRET_PLACEHOLDER = /\{\{\s*\$secret\./;
|
|
10479
10804
|
function containsSecretPlaceholder(request) {
|
|
@@ -10677,13 +11002,17 @@ var RequestSkipped = class extends SendBlockedError {
|
|
|
10677
11002
|
scriptLogs;
|
|
10678
11003
|
nextRequest;
|
|
10679
11004
|
};
|
|
10680
|
-
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.`;
|
|
10681
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.";
|
|
10682
11009
|
function sentBodyPreview(body3) {
|
|
10683
11010
|
if (body3 === void 0 || typeof body3 === "string") return body3;
|
|
10684
11011
|
const file = (blob) => `(${blob.size} bytes${blob.type ? `, ${blob.type}` : ""})`;
|
|
10685
11012
|
if (body3 instanceof FormData) {
|
|
10686
|
-
return [...body3].map(
|
|
11013
|
+
return [...body3].map(
|
|
11014
|
+
([name, value]) => typeof value === "string" ? `${name}=${value}` : `${name}=@${value.name} ${file(value)}`
|
|
11015
|
+
).join("\n");
|
|
10687
11016
|
}
|
|
10688
11017
|
return `@file ${file(body3)}`;
|
|
10689
11018
|
}
|
|
@@ -10704,7 +11033,10 @@ function bodyText(body3) {
|
|
|
10704
11033
|
return stringifyJson(body3) ?? "";
|
|
10705
11034
|
}
|
|
10706
11035
|
function lastNext(runs) {
|
|
10707
|
-
return runs.reduce(
|
|
11036
|
+
return runs.reduce(
|
|
11037
|
+
(last, run) => run.result.nextRequest !== void 0 ? run.result.nextRequest : last,
|
|
11038
|
+
void 0
|
|
11039
|
+
);
|
|
10708
11040
|
}
|
|
10709
11041
|
function scriptsToRun(spec, kind) {
|
|
10710
11042
|
const label = kind === "pre" ? "Pre-request script" : "Post-response script";
|
|
@@ -10716,7 +11048,11 @@ function scriptsToRun(spec, kind) {
|
|
|
10716
11048
|
function scriptRows(result, name, redactor) {
|
|
10717
11049
|
if (!result) return [];
|
|
10718
11050
|
const rows2 = result.error === void 0 ? result.testResults : [...result.testResults, { label: name, passed: false, error: result.error, source: "script" }];
|
|
10719
|
-
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
|
+
}));
|
|
10720
11056
|
}
|
|
10721
11057
|
async function exchange(spec, ports) {
|
|
10722
11058
|
const createdAt = Date.now();
|
|
@@ -10727,7 +11063,11 @@ async function exchange(spec, ports) {
|
|
|
10727
11063
|
let undo = () => Promise.resolve();
|
|
10728
11064
|
const preScripts = spec.runScripts ? scriptsToRun(spec, "pre") : [];
|
|
10729
11065
|
if (preScripts.length) {
|
|
10730
|
-
const before = {
|
|
11066
|
+
const before = {
|
|
11067
|
+
environment: ports.environment()?.vars ?? [],
|
|
11068
|
+
global: ports.globals(),
|
|
11069
|
+
collection: spec.collection?.variables() ?? []
|
|
11070
|
+
};
|
|
10731
11071
|
const touched = emptyChanges();
|
|
10732
11072
|
const restoreSecrets = [];
|
|
10733
11073
|
const was = (rows2, names) => names.map(({ key }) => {
|
|
@@ -10735,13 +11075,27 @@ async function exchange(spec, ports) {
|
|
|
10735
11075
|
return { key, value, ...json && { json } };
|
|
10736
11076
|
});
|
|
10737
11077
|
undo = async () => {
|
|
10738
|
-
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
|
+
);
|
|
10739
11088
|
for (const restore of restoreSecrets.reverse()) await restore();
|
|
10740
11089
|
};
|
|
10741
11090
|
timings.preScriptMs = 0;
|
|
10742
11091
|
for (const { name, text: text3 } of preScripts) {
|
|
10743
11092
|
const scriptStarted = performance.now();
|
|
10744
|
-
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
|
+
);
|
|
10745
11099
|
timings.preScriptMs += Math.round(performance.now() - scriptStarted);
|
|
10746
11100
|
preRuns.push({ name, result });
|
|
10747
11101
|
const { changes } = result;
|
|
@@ -10749,13 +11103,22 @@ async function exchange(spec, ports) {
|
|
|
10749
11103
|
for (const scope of ["environment", "global", "collection"]) touched[scope].push(...changes[scope]);
|
|
10750
11104
|
if (result.error !== void 0) {
|
|
10751
11105
|
const nothing = new Redactor([]);
|
|
10752
|
-
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
|
+
);
|
|
10753
11112
|
}
|
|
10754
11113
|
if (result.request) template = withScriptRequest(template, result.request);
|
|
10755
11114
|
}
|
|
10756
11115
|
if (spec.iteration && preRuns.some((run) => run.result.skipRequest)) {
|
|
10757
11116
|
const nothing = new Redactor([]);
|
|
10758
|
-
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
|
+
);
|
|
10759
11122
|
}
|
|
10760
11123
|
}
|
|
10761
11124
|
let request;
|
|
@@ -10776,14 +11139,19 @@ async function exchange(spec, ports) {
|
|
|
10776
11139
|
const jar = ports.cookies;
|
|
10777
11140
|
const cookies = jar && {
|
|
10778
11141
|
for: (url) => {
|
|
10779
|
-
const found = jar.for(url).filter(
|
|
11142
|
+
const found = jar.for(url).filter(
|
|
11143
|
+
(cookie) => !containsSecretPlaceholder({ url: "", headers: [["Cookie", `${cookie.name}=${cookie.value}`]] })
|
|
11144
|
+
);
|
|
10780
11145
|
sentCookies ??= found;
|
|
10781
11146
|
for (const cookie of found) cookieValues.add(cookie.value);
|
|
10782
11147
|
return found;
|
|
10783
11148
|
},
|
|
10784
11149
|
store: (url, setCookies) => void jar.store(url, setCookies, request.secrets)
|
|
10785
11150
|
};
|
|
10786
|
-
const policy = {
|
|
11151
|
+
const policy = {
|
|
11152
|
+
route: spec.route ?? ports.transport.route(),
|
|
11153
|
+
...spec.askRoute && { ask: spec.askRoute }
|
|
11154
|
+
};
|
|
10787
11155
|
const transmit = async () => {
|
|
10788
11156
|
startedAt = performance.now();
|
|
10789
11157
|
sentCookies = void 0;
|
|
@@ -10824,7 +11192,7 @@ async function exchange(spec, ports) {
|
|
|
10824
11192
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
10825
11193
|
timings.requestMs = durationMs;
|
|
10826
11194
|
ports.watch?.received(request.url);
|
|
10827
|
-
const shaped = outcome instanceof TransportError ? shapeFailure(outcome,
|
|
11195
|
+
const shaped = outcome instanceof TransportError ? shapeFailure(outcome, ports.page, ports.online?.()) : shapeResponse(outcome, ports.page);
|
|
10828
11196
|
const redactor = new Redactor([...request.secrets, ...request.credentials, ...cookieValues]);
|
|
10829
11197
|
const secrets = new Redactor(request.secrets);
|
|
10830
11198
|
const post = await runPostScriptAndAssertions(
|
|
@@ -10842,7 +11210,14 @@ async function exchange(spec, ports) {
|
|
|
10842
11210
|
notes,
|
|
10843
11211
|
secrets,
|
|
10844
11212
|
timings,
|
|
10845
|
-
{
|
|
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
|
+
}
|
|
10846
11221
|
);
|
|
10847
11222
|
const runs = [...preRuns, ...post.scripts];
|
|
10848
11223
|
const testResults = [...runs.flatMap((run) => scriptRows(run.result, run.name, redactor)), ...post.assertions];
|
|
@@ -10862,7 +11237,10 @@ async function exchange(spec, ports) {
|
|
|
10862
11237
|
...preview !== void 0 && { bodyPreview: keep(preview).text }
|
|
10863
11238
|
},
|
|
10864
11239
|
route: outcome instanceof TransportError ? policy.took ?? ports.transport.first(policy.route) : outcome.route,
|
|
10865
|
-
durationMs
|
|
11240
|
+
durationMs,
|
|
11241
|
+
...spec.info && {
|
|
11242
|
+
sentFrom: { name: redactor.text(spec.info.requestName), path: spec.info.path.map((name) => redactor.text(name)) }
|
|
11243
|
+
}
|
|
10866
11244
|
};
|
|
10867
11245
|
if (!(outcome instanceof TransportError)) {
|
|
10868
11246
|
history.response = {
|
|
@@ -10876,7 +11254,25 @@ async function exchange(spec, ports) {
|
|
|
10876
11254
|
history.error = redactor.text(shaped.historyError);
|
|
10877
11255
|
}
|
|
10878
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;
|
|
10879
11274
|
return {
|
|
11275
|
+
record,
|
|
10880
11276
|
durationMs,
|
|
10881
11277
|
timings,
|
|
10882
11278
|
...!(outcome instanceof TransportError) && outcome.timings && { relayTimings: outcome.timings },
|
|
@@ -10888,11 +11284,44 @@ async function exchange(spec, ports) {
|
|
|
10888
11284
|
...nextRequest !== void 0 && { nextRequest }
|
|
10889
11285
|
};
|
|
10890
11286
|
}
|
|
10891
|
-
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) {
|
|
10892
11322
|
const body3 = decodeEnvelope(envelope);
|
|
10893
11323
|
const isError = envelope.status < 200 || envelope.status >= 300;
|
|
10894
|
-
const
|
|
10895
|
-
const shown = isError ? body3 ?? failureText : body3;
|
|
11324
|
+
const shown = body3;
|
|
10896
11325
|
const bodyIsJson = isJsonPayload(shown);
|
|
10897
11326
|
const text3 = bodyIsJson ? serializeJsonPayload(shown) : stringifyPayload(shown);
|
|
10898
11327
|
const header = (name) => envelope.headers.find(([key]) => key.toLowerCase() === name)?.[1];
|
|
@@ -10902,7 +11331,6 @@ function shapeResponse(envelope, url, page) {
|
|
|
10902
11331
|
headers: Object.fromEntries(envelope.headers),
|
|
10903
11332
|
headerList: envelope.headers,
|
|
10904
11333
|
bodyText: body3 === void 0 || body3 === null || body3 instanceof BinaryBody ? void 0 : text3,
|
|
10905
|
-
historyError: isError ? failureText : void 0,
|
|
10906
11334
|
response: {
|
|
10907
11335
|
isError,
|
|
10908
11336
|
statusCode: envelope.status,
|
|
@@ -10911,34 +11339,36 @@ function shapeResponse(envelope, url, page) {
|
|
|
10911
11339
|
dataText: isError ? "" : text3,
|
|
10912
11340
|
errorText: isError ? text3 : "",
|
|
10913
11341
|
headersView: envelope.headers.map(([name, value]) => ({ name, value })).sort((a, b) => a.name.localeCompare(b.name)),
|
|
10914
|
-
contentLength: Number.isFinite(contentLength) ? contentLength :
|
|
11342
|
+
contentLength: Number.isFinite(contentLength) ? contentLength : envelope.sizes.decoded,
|
|
10915
11343
|
binary: body3 instanceof BinaryBody ? body3 : void 0,
|
|
10916
11344
|
redirectedTo: envelope.redirected ? envelope.finalUrl : void 0,
|
|
10917
11345
|
route: envelope.route,
|
|
10918
11346
|
contentType: header("content-type"),
|
|
10919
|
-
// Outside a page nothing is withheld: no browser stands between.
|
|
10920
|
-
|
|
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() === "*"),
|
|
10921
11350
|
...envelope.stream && { stream: envelope.stream },
|
|
10922
11351
|
...envelope.redirects && { redirects: envelope.redirects },
|
|
10923
11352
|
...envelope.tlsInsecure && { tlsInsecure: true }
|
|
10924
11353
|
}
|
|
10925
11354
|
};
|
|
10926
11355
|
}
|
|
10927
|
-
function shapeFailure(failure,
|
|
11356
|
+
function shapeFailure(failure, page, online) {
|
|
10928
11357
|
const network = failure.kind === "network";
|
|
10929
|
-
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;
|
|
10930
11360
|
return {
|
|
10931
|
-
body:
|
|
11361
|
+
body: said2 ? void 0 : failure.message,
|
|
10932
11362
|
headers: {},
|
|
10933
11363
|
headerList: [],
|
|
10934
|
-
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,
|
|
10935
11365
|
response: {
|
|
10936
11366
|
isError: true,
|
|
10937
|
-
statusCode,
|
|
10938
|
-
statusText:
|
|
11367
|
+
statusCode: void 0,
|
|
11368
|
+
statusText: "",
|
|
10939
11369
|
bodyIsJson: false,
|
|
10940
11370
|
dataText: "",
|
|
10941
|
-
errorText
|
|
11371
|
+
errorText,
|
|
10942
11372
|
headersView: []
|
|
10943
11373
|
}
|
|
10944
11374
|
};
|
|
@@ -10947,13 +11377,21 @@ async function runPostScriptAndAssertions(ports, spec, sent, notes, variables2,
|
|
|
10947
11377
|
const scripts2 = [];
|
|
10948
11378
|
for (const { name, text: text3 } of spec.runScripts ? scriptsToRun(spec, "post") : []) {
|
|
10949
11379
|
const scriptStarted = performance.now();
|
|
10950
|
-
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
|
+
);
|
|
10951
11386
|
timings.postScriptMs = (timings.postScriptMs ?? 0) + Math.round(performance.now() - scriptStarted);
|
|
10952
11387
|
scripts2.push({ name, result });
|
|
10953
11388
|
await applyChanges(ports, spec, result.changes, notes, variables2);
|
|
10954
11389
|
}
|
|
10955
11390
|
const { statusCode, body: body3, headers: headers2, durationMs } = response;
|
|
10956
|
-
return {
|
|
11391
|
+
return {
|
|
11392
|
+
scripts: scripts2,
|
|
11393
|
+
assertions: spec.tests.length ? runAssertions(spec.tests, { statusCode, body: body3, headers: headers2, durationMs }) : []
|
|
11394
|
+
};
|
|
10957
11395
|
}
|
|
10958
11396
|
function environmentSnapshot(ports) {
|
|
10959
11397
|
return scriptVariables(ports.environment()?.vars ?? []);
|
|
@@ -10966,17 +11404,31 @@ function scriptExtras(ports, spec, eventName, request) {
|
|
|
10966
11404
|
...spec.iteration && { data: scriptVariables(spec.iteration.data) },
|
|
10967
11405
|
collection: scriptVariables(spec.collection?.variables() ?? []),
|
|
10968
11406
|
request,
|
|
10969
|
-
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
|
+
},
|
|
10970
11413
|
send: (request2) => sendForScript(ports, request2, spec.signal, spec.route)
|
|
10971
11414
|
};
|
|
10972
11415
|
}
|
|
10973
11416
|
async function sendForScript(ports, request, signal, route = ports.transport.route()) {
|
|
10974
11417
|
if (containsSecretPlaceholder(request)) {
|
|
10975
|
-
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
|
+
);
|
|
10976
11424
|
}
|
|
10977
11425
|
const startedAt = performance.now();
|
|
10978
|
-
const envelope = await ports.transport.send(
|
|
10979
|
-
|
|
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);
|
|
10980
11432
|
return scriptResponse({
|
|
10981
11433
|
statusCode: envelope.status,
|
|
10982
11434
|
statusText: envelope.statusText,
|
|
@@ -10989,7 +11441,12 @@ async function sendForScript(ports, request, signal, route = ports.transport.rou
|
|
|
10989
11441
|
async function applyChanges(ports, spec, changes, notes, secrets) {
|
|
10990
11442
|
const active = ports.environment();
|
|
10991
11443
|
const replaced = /* @__PURE__ */ new Map();
|
|
10992
|
-
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
|
+
}));
|
|
10993
11450
|
const environment2 = await kept("pm.environment", active?.vars ?? [], changes.environment);
|
|
10994
11451
|
const global = await kept("pm.globals", ports.globals(), changes.global);
|
|
10995
11452
|
const collection = await kept("pm.collectionVariables", spec.collection?.variables() ?? [], changes.collection);
|
|
@@ -11000,7 +11457,10 @@ async function applyChanges(ports, spec, changes, notes, secrets) {
|
|
|
11000
11457
|
if (global.length) await ports.changeGlobals(global);
|
|
11001
11458
|
if (collection.length) {
|
|
11002
11459
|
if (spec.collection) await spec.collection.change(collection);
|
|
11003
|
-
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
|
+
);
|
|
11004
11464
|
}
|
|
11005
11465
|
return async () => {
|
|
11006
11466
|
for (const [id, plaintext] of replaced) await ports.vault.replaceSecret(id, plaintext);
|
|
@@ -11020,7 +11480,8 @@ async function intoVault(ports, api, rows2, changes, notes, replaced) {
|
|
|
11020
11480
|
continue;
|
|
11021
11481
|
}
|
|
11022
11482
|
const old = await ports.vault.readSecret(id);
|
|
11023
|
-
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");
|
|
11024
11485
|
else if (!replaced.has(id)) replaced.set(id, old);
|
|
11025
11486
|
}
|
|
11026
11487
|
}
|
|
@@ -11063,7 +11524,9 @@ function stringifyPayload(payload) {
|
|
|
11063
11524
|
// ../core/src/send/build.ts
|
|
11064
11525
|
var UnresolvedVariablesError = class extends SendBlockedError {
|
|
11065
11526
|
constructor(names) {
|
|
11066
|
-
super(
|
|
11527
|
+
super(
|
|
11528
|
+
`${names.map((name) => `{{${name}}}`).join(", ")} ${names.length === 1 ? "has" : "have"} no value. The request was not sent.`
|
|
11529
|
+
);
|
|
11067
11530
|
this.names = names;
|
|
11068
11531
|
}
|
|
11069
11532
|
names;
|
|
@@ -11145,7 +11608,8 @@ function buildRequest(content, context) {
|
|
|
11145
11608
|
const to = hostOf(endpointText, scopes());
|
|
11146
11609
|
if (from === void 0 || from === to || composed?.accepted?.has(to)) return withSecrets();
|
|
11147
11610
|
return (context.askHost?.(from, to) ?? Promise.resolve(false)).then((accepted) => {
|
|
11148
|
-
if (!accepted)
|
|
11611
|
+
if (!accepted)
|
|
11612
|
+
throw new SendDeclinedError("The request was not sent, and what the pre-request script set was undone.");
|
|
11149
11613
|
composed?.accepted?.add(to);
|
|
11150
11614
|
return withSecrets();
|
|
11151
11615
|
});
|
|
@@ -11155,11 +11619,17 @@ function build(content, resolver, context) {
|
|
|
11155
11619
|
const auth3 = resolveAuth(sentAuth(content.auth, context.ancestors), resolve);
|
|
11156
11620
|
const withoutValue = [...resolver.unresolved];
|
|
11157
11621
|
const sent = isBodyMethod(content.method) ? resolveBody(content.body, resolve, context) : void 0;
|
|
11158
|
-
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
|
+
);
|
|
11159
11626
|
const oauth = auth3.type === "oauth2" ? oauthOf(content.auth, context.ancestors, context.scopes()) : null;
|
|
11160
|
-
if (auth3.type !== "oauth2" || !oauth)
|
|
11627
|
+
if (auth3.type !== "oauth2" || !oauth)
|
|
11628
|
+
return sent instanceof Promise ? sent.then((body3) => finish(body3)) : finish(sent);
|
|
11161
11629
|
if (withoutValue.length) {
|
|
11162
|
-
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
|
+
);
|
|
11163
11633
|
}
|
|
11164
11634
|
if (!context.oauthToken) throw new SendBlockedError("OAuth 2.0 tokens are not fetched here. Nothing was sent.");
|
|
11165
11635
|
const token = context.oauthToken(auth3, oauth.key, context.renewToken ?? false).catch((error) => {
|
|
@@ -11170,18 +11640,30 @@ function build(content, resolver, context) {
|
|
|
11170
11640
|
function sign(built2, auth3) {
|
|
11171
11641
|
if (auth3.type !== "awsv4" || !URL.canParse(built2.url)) return built2;
|
|
11172
11642
|
if (built2.body instanceof FormData) {
|
|
11173
|
-
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
|
+
);
|
|
11174
11646
|
}
|
|
11175
|
-
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
|
+
);
|
|
11176
11651
|
const add = async (body3) => {
|
|
11177
11652
|
const own3 = built2.headers.filter(([name]) => /^(content-type$|x-amz-)/i.test(name));
|
|
11178
|
-
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
|
+
);
|
|
11179
11658
|
const replaced = new Set(signature.map(([name]) => name.toLowerCase()));
|
|
11180
11659
|
return {
|
|
11181
11660
|
...built2,
|
|
11182
11661
|
headers: [...built2.headers.filter(([name]) => !replaced.has(name.toLowerCase())), ...signature],
|
|
11183
11662
|
// The signature's header too: a server may send it back.
|
|
11184
|
-
credentials: [
|
|
11663
|
+
credentials: [
|
|
11664
|
+
...built2.credentials,
|
|
11665
|
+
...signature.filter(([name]) => isCredentialHeader(name)).map(([, value]) => value)
|
|
11666
|
+
]
|
|
11185
11667
|
};
|
|
11186
11668
|
};
|
|
11187
11669
|
if (built2.body instanceof Blob) return built2.body.arrayBuffer().then((bytes) => add(new Uint8Array(bytes)));
|
|
@@ -11213,8 +11695,16 @@ function assemble(content, sent, resolver, extra) {
|
|
|
11213
11695
|
body: sent?.data,
|
|
11214
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.
|
|
11215
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
|
+
],
|
|
11216
11703
|
// Also what a credential header carries, typed by hand or built from the Auth tab: a server may send it back.
|
|
11217
|
-
credentials: [
|
|
11704
|
+
credentials: [
|
|
11705
|
+
...credentialsOf(auth3),
|
|
11706
|
+
...headers2.filter(([name]) => isCredentialHeader(name)).map(([, value]) => value)
|
|
11707
|
+
].filter((value) => value)
|
|
11218
11708
|
};
|
|
11219
11709
|
}
|
|
11220
11710
|
function resolveBody(body3, resolve, context) {
|
|
@@ -11235,7 +11725,11 @@ function resolveBody(body3, resolve, context) {
|
|
|
11235
11725
|
return { data: new URLSearchParams(pairs2).toString(), contentType: "application/x-www-form-urlencoded" };
|
|
11236
11726
|
}
|
|
11237
11727
|
case "multipart":
|
|
11238
|
-
return multipartForm(
|
|
11728
|
+
return multipartForm(
|
|
11729
|
+
(body3.multipart ?? []).filter((item) => item.enabled),
|
|
11730
|
+
resolve,
|
|
11731
|
+
context
|
|
11732
|
+
);
|
|
11239
11733
|
case "binary":
|
|
11240
11734
|
return binaryFile(body3.binary, context);
|
|
11241
11735
|
}
|
|
@@ -11278,10 +11772,19 @@ async function runOutcome(send, declined) {
|
|
|
11278
11772
|
exchange: result.history
|
|
11279
11773
|
};
|
|
11280
11774
|
} catch (error) {
|
|
11281
|
-
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
|
+
};
|
|
11282
11783
|
if (error instanceof SendDeclinedError) return { outcome: { error: error.message, tests: [], end: declined } };
|
|
11283
|
-
if (error instanceof PreRequestScriptError)
|
|
11284
|
-
|
|
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: [] } };
|
|
11285
11788
|
throw error;
|
|
11286
11789
|
}
|
|
11287
11790
|
}
|
|
@@ -11308,7 +11811,14 @@ function validateWorkspaceFile(value) {
|
|
|
11308
11811
|
}
|
|
11309
11812
|
return records;
|
|
11310
11813
|
};
|
|
11311
|
-
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);
|
|
11312
11822
|
if (errors.length) return errors;
|
|
11313
11823
|
const idOf2 = (doc) => isObject2(doc["meta"]) ? doc["meta"]["id"] : void 0;
|
|
11314
11824
|
const known = new Set(collections.map(idOf2));
|
|
@@ -11321,12 +11831,21 @@ function validateWorkspaceFile(value) {
|
|
|
11321
11831
|
folders: folders.filter((folder) => folder["collectionId"] === id),
|
|
11322
11832
|
requests: requests.filter((request) => request["collectionId"] === id)
|
|
11323
11833
|
});
|
|
11324
|
-
errors.push(
|
|
11325
|
-
|
|
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.`);
|
|
11326
11841
|
}
|
|
11327
|
-
for (const [kind, docs] of [
|
|
11842
|
+
for (const [kind, docs] of [
|
|
11843
|
+
["folders", folders],
|
|
11844
|
+
["requests", requests]
|
|
11845
|
+
]) {
|
|
11328
11846
|
docs.forEach((doc, index) => {
|
|
11329
|
-
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.`);
|
|
11330
11849
|
});
|
|
11331
11850
|
}
|
|
11332
11851
|
errors.push(...validateEnvironmentExport({ $id: ENVIRONMENTS_FORMAT, environments }).errors ?? []);
|
|
@@ -11335,9 +11854,14 @@ function validateWorkspaceFile(value) {
|
|
|
11335
11854
|
const key = record["key"];
|
|
11336
11855
|
if (key === META_STATE_KEY) {
|
|
11337
11856
|
const active = record["activeEnvironmentId"];
|
|
11338
|
-
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.");
|
|
11339
11859
|
} else if (key === META_GLOBALS_KEY) {
|
|
11340
|
-
errors.push(
|
|
11860
|
+
errors.push(
|
|
11861
|
+
...validateRows(record["variables"], "stores.meta.globals.variables").map(
|
|
11862
|
+
(issue) => `${issue.path}: ${issue.message}`
|
|
11863
|
+
)
|
|
11864
|
+
);
|
|
11341
11865
|
} else if (key === META_VAULT_KEY) {
|
|
11342
11866
|
vault = record;
|
|
11343
11867
|
} else {
|
|
@@ -11348,7 +11872,8 @@ function validateWorkspaceFile(value) {
|
|
|
11348
11872
|
const issues = validateVaultFile({ $id: VAULT_FILE_FORMAT, vault, secrets });
|
|
11349
11873
|
errors.push(...issues.map((issue) => `vault: ${issue.path}: ${issue.message}`));
|
|
11350
11874
|
secrets.forEach((secret, index) => {
|
|
11351
|
-
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.`);
|
|
11352
11877
|
});
|
|
11353
11878
|
}
|
|
11354
11879
|
const history = own(stores, "history");
|
|
@@ -11357,16 +11882,47 @@ function validateWorkspaceFile(value) {
|
|
|
11357
11882
|
const path = `stores.history[${index}]`;
|
|
11358
11883
|
const sent = entry["sent"];
|
|
11359
11884
|
if (typeof entry["createdAt"] !== "number") errors.push(`${path}.createdAt: Value must be a number.`);
|
|
11360
|
-
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.`);
|
|
11361
11887
|
if (!isObject2(sent) || typeof sent["method"] !== "string" || typeof sent["url"] !== "string" || !Array.isArray(sent["headers"])) {
|
|
11362
11888
|
errors.push(`${path}.sent: Value must hold a method, a URL and headers.`);
|
|
11363
11889
|
}
|
|
11364
|
-
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
|
+
}
|
|
11365
11908
|
});
|
|
11366
11909
|
}
|
|
11367
11910
|
return errors;
|
|
11368
11911
|
}
|
|
11369
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
|
+
|
|
11370
11926
|
// ../core/src/run/data-file.ts
|
|
11371
11927
|
var DataFileError = class extends Error {
|
|
11372
11928
|
name = "DataFileError";
|
|
@@ -11408,28 +11964,37 @@ function parseDataFile(text3, fileName) {
|
|
|
11408
11964
|
if (/\.json$/i.test(fileName)) {
|
|
11409
11965
|
const parsed2 = parseJson(text3);
|
|
11410
11966
|
const isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11411
|
-
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.");
|
|
11412
11969
|
return parsed2.value.map(
|
|
11413
|
-
(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
|
+
)
|
|
11414
11973
|
);
|
|
11415
11974
|
}
|
|
11416
11975
|
const [names, ...records] = parseCsv(text3);
|
|
11417
|
-
if (!names?.some((name) => name))
|
|
11418
|
-
|
|
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
|
+
);
|
|
11419
11981
|
}
|
|
11420
11982
|
|
|
11421
|
-
// ../core/src/import/import-error.ts
|
|
11422
|
-
var ImportError = class extends Error {
|
|
11423
|
-
constructor(message, issues = []) {
|
|
11424
|
-
super(message);
|
|
11425
|
-
this.issues = issues;
|
|
11426
|
-
}
|
|
11427
|
-
issues;
|
|
11428
|
-
name = "ImportError";
|
|
11429
|
-
};
|
|
11430
|
-
|
|
11431
11983
|
// ../core/src/import/curl.ts
|
|
11432
|
-
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
|
+
};
|
|
11433
11998
|
function ansiEscape(text3, at) {
|
|
11434
11999
|
const numbered = [
|
|
11435
12000
|
[/^x([0-9a-fA-F]{1,2})/, 16],
|
|
@@ -11562,7 +12127,33 @@ var FLAGS = {
|
|
|
11562
12127
|
"--insecure": "insecure",
|
|
11563
12128
|
"--compressed": "compressed"
|
|
11564
12129
|
};
|
|
11565
|
-
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
|
+
]);
|
|
11566
12157
|
var SKIPPED_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
11567
12158
|
"-o",
|
|
11568
12159
|
"--output",
|
|
@@ -11644,7 +12235,9 @@ function formPart(value, warnings) {
|
|
|
11644
12235
|
if (quotedFile) return { name, fileName: quotedFile[1].replace(/\\(.)/g, "$1") };
|
|
11645
12236
|
if (content.startsWith("@")) return { name, fileName: content.slice(1).split(/[;,]/)[0] };
|
|
11646
12237
|
if (content.startsWith("<")) {
|
|
11647
|
-
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
|
+
);
|
|
11648
12241
|
return { name, value: "" };
|
|
11649
12242
|
}
|
|
11650
12243
|
return { name, value: content.replace(/;type=[^;]*$/, "") };
|
|
@@ -11727,7 +12320,8 @@ function parseCurl(text3) {
|
|
|
11727
12320
|
} else if (word.startsWith("--")) {
|
|
11728
12321
|
if (Object.hasOwn(VALUE_OPTIONS, word)) take(VALUE_OPTIONS[word], valueOf(word));
|
|
11729
12322
|
else if (Object.hasOwn(FLAGS, word)) ({ get, head } = flag(FLAGS[word], { get, head }, warnings));
|
|
11730
|
-
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.`);
|
|
11731
12325
|
else if (!QUIET_FLAGS.has(word)) warnings.push(`${word} was left out: the app does not know this option.`);
|
|
11732
12326
|
} else {
|
|
11733
12327
|
for (let at = 1; at < word.length; at++) {
|
|
@@ -11747,14 +12341,18 @@ function parseCurl(text3) {
|
|
|
11747
12341
|
}
|
|
11748
12342
|
}
|
|
11749
12343
|
if (!urls.length) throw new ImportError("The cURL command has no address.");
|
|
11750
|
-
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
|
+
);
|
|
11751
12348
|
let url = urls[0];
|
|
11752
12349
|
const type = (contentType(headers2) ?? "").toLowerCase();
|
|
11753
12350
|
let body3 = { mode: "none" };
|
|
11754
12351
|
const files = data.flatMap((datum) => "file" in datum ? [datum.file] : []);
|
|
11755
12352
|
if (parts.length) {
|
|
11756
12353
|
body3 = { mode: "multipart", parts };
|
|
11757
|
-
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.");
|
|
11758
12356
|
} else if (get) {
|
|
11759
12357
|
const query = data.flatMap((datum) => "text" in datum ? [datum.text] : []).join("&");
|
|
11760
12358
|
if (files.length) warnings.push(`The data of the file ${files.join(", ")} was left out: the file was not read.`);
|
|
@@ -11767,14 +12365,21 @@ function parseCurl(text3) {
|
|
|
11767
12365
|
body3 = !type || type.startsWith("application/x-www-form-urlencoded") ? { mode: "urlencoded", fields: [...new URLSearchParams(wire)] } : { mode: "raw", text: wire };
|
|
11768
12366
|
}
|
|
11769
12367
|
method ??= head ? "HEAD" : body3.mode === "none" ? "GET" : "POST";
|
|
11770
|
-
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
|
+
);
|
|
11771
12372
|
return { request: { method, url, headers: headers2, body: body3 }, ...basic && { basic }, warnings };
|
|
11772
12373
|
}
|
|
11773
12374
|
function flag(kind, state, warnings) {
|
|
11774
12375
|
if (kind === "get") return { ...state, get: true };
|
|
11775
12376
|
if (kind === "head") return { ...state, head: true };
|
|
11776
|
-
if (kind === "insecure")
|
|
11777
|
-
|
|
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.");
|
|
11778
12383
|
return state;
|
|
11779
12384
|
}
|
|
11780
12385
|
function languageOf(type) {
|
|
@@ -11793,8 +12398,15 @@ function curlRequest(parsed2) {
|
|
|
11793
12398
|
const language = languageOf(type.toLowerCase());
|
|
11794
12399
|
const row = (key, value) => ({ key, value, enabled: true });
|
|
11795
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;
|
|
11796
|
-
const headers2 = request.headers.filter(
|
|
11797
|
-
|
|
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
|
+
};
|
|
11798
12410
|
if (basic) content.auth = { type: "basic", ...basic };
|
|
11799
12411
|
if (body3.mode === "raw") {
|
|
11800
12412
|
content.body = { mode: "raw", raw: { language, text: body3.text } };
|
|
@@ -11803,12 +12415,23 @@ function curlRequest(parsed2) {
|
|
|
11803
12415
|
} else if (body3.mode === "multipart") {
|
|
11804
12416
|
content.body = {
|
|
11805
12417
|
mode: "multipart",
|
|
11806
|
-
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
|
+
)
|
|
11807
12421
|
};
|
|
11808
|
-
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
|
+
);
|
|
11809
12427
|
} else if (body3.mode === "binary") {
|
|
11810
|
-
content.body = {
|
|
11811
|
-
|
|
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
|
+
);
|
|
11812
12435
|
}
|
|
11813
12436
|
return { content, warnings };
|
|
11814
12437
|
}
|
|
@@ -11829,16 +12452,23 @@ function bodyOf(postData, warnings) {
|
|
|
11829
12452
|
const params = list(own2(postData, "params"));
|
|
11830
12453
|
if (type.startsWith("multipart/form-data")) {
|
|
11831
12454
|
if (!params.length) {
|
|
11832
|
-
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
|
+
);
|
|
11833
12458
|
return { mode: "raw", text: text(own2(postData, "text")) };
|
|
11834
12459
|
}
|
|
11835
12460
|
return {
|
|
11836
12461
|
mode: "multipart",
|
|
11837
|
-
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
|
+
)
|
|
11838
12465
|
};
|
|
11839
12466
|
}
|
|
11840
12467
|
if (type.startsWith("application/x-www-form-urlencoded")) {
|
|
11841
|
-
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
|
+
};
|
|
11842
12472
|
}
|
|
11843
12473
|
const body3 = text(own2(postData, "text"));
|
|
11844
12474
|
if (own2(postData, "encoding") === "base64") {
|
|
@@ -11853,11 +12483,18 @@ function parseHar(value) {
|
|
|
11853
12483
|
return entries.flatMap((entry, index) => {
|
|
11854
12484
|
const request = own2(entry, "request");
|
|
11855
12485
|
const [method, url] = isObject3(request) ? [text(own2(request, "method")).toUpperCase(), text(own2(request, "url"))] : ["", ""];
|
|
11856
|
-
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.`);
|
|
11857
12488
|
const warnings = [];
|
|
11858
12489
|
const headers2 = list(own2(request, "headers")).map((header) => [text(own2(header, "name")), text(own2(header, "value"))]).filter(([name]) => name && !name.startsWith(":"));
|
|
11859
12490
|
const path = URL.canParse(url) ? new URL(url).pathname : url;
|
|
11860
|
-
return [
|
|
12491
|
+
return [
|
|
12492
|
+
{
|
|
12493
|
+
name: `${method} ${path}`,
|
|
12494
|
+
request: { method, url, headers: headers2, body: bodyOf(own2(request, "postData"), warnings) },
|
|
12495
|
+
warnings
|
|
12496
|
+
}
|
|
12497
|
+
];
|
|
11861
12498
|
});
|
|
11862
12499
|
}
|
|
11863
12500
|
|
|
@@ -11890,7 +12527,12 @@ function addressParts(raw) {
|
|
|
11890
12527
|
const host = colon === -1 ? authority : authority.slice(0, colon);
|
|
11891
12528
|
return {
|
|
11892
12529
|
...protocol !== void 0 && { protocol: back(protocol) },
|
|
11893
|
-
...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
|
+
},
|
|
11894
12536
|
host: host.startsWith("[") ? [back(host)] : host.split(".").map(back),
|
|
11895
12537
|
...port !== void 0 && { port: back(port) },
|
|
11896
12538
|
...path !== void 0 && { path: path.split("/").map(back) },
|
|
@@ -11925,7 +12567,11 @@ var description = (value) => {
|
|
|
11925
12567
|
const words = isObject4(value) ? text2(field3(value, "content")) : text2(value);
|
|
11926
12568
|
return words ? { description: words } : {};
|
|
11927
12569
|
};
|
|
11928
|
-
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
|
+
}));
|
|
11929
12575
|
function headers(value) {
|
|
11930
12576
|
if (typeof value !== "string") return rows(value);
|
|
11931
12577
|
return value.split(/\r?\n/).filter((line2) => line2.trim()).map((line2) => {
|
|
@@ -11950,15 +12596,30 @@ function auth(value, item, warnings, top) {
|
|
|
11950
12596
|
case "apikey":
|
|
11951
12597
|
return { type, key: read("key"), value: read("value"), in: read("in") === "query" ? "query" : "header" };
|
|
11952
12598
|
case "awsv4":
|
|
11953
|
-
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
|
+
};
|
|
11954
12607
|
case "oauth2": {
|
|
11955
12608
|
const grant = OAUTH_GRANTS_OF.get(read("grant_type"));
|
|
11956
12609
|
if (!grant) {
|
|
11957
|
-
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
|
+
});
|
|
11958
12614
|
return { type: "none" };
|
|
11959
12615
|
}
|
|
11960
|
-
if (read("accessToken"))
|
|
11961
|
-
|
|
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
|
+
});
|
|
11962
12623
|
return {
|
|
11963
12624
|
type,
|
|
11964
12625
|
grant,
|
|
@@ -11974,7 +12635,10 @@ function auth(value, item, warnings, top) {
|
|
|
11974
12635
|
};
|
|
11975
12636
|
}
|
|
11976
12637
|
default:
|
|
11977
|
-
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
|
+
});
|
|
11978
12642
|
return { type: "none" };
|
|
11979
12643
|
}
|
|
11980
12644
|
}
|
|
@@ -11992,21 +12656,30 @@ function scripts(value, item, warnings) {
|
|
|
11992
12656
|
return Array.isArray(exec) ? exec.map(text2).join("\n") : text2(exec);
|
|
11993
12657
|
}).join("\n");
|
|
11994
12658
|
const others = objects(value).map((event) => text2(field3(event, "listen"))).filter((listen) => listen !== "prerequest" && listen !== "test");
|
|
11995
|
-
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
|
+
});
|
|
11996
12664
|
return { pre: of2("prerequest"), post: of2("test") };
|
|
11997
12665
|
}
|
|
11998
12666
|
function address(value, item, warnings) {
|
|
11999
12667
|
const inText = (url2) => (addressParts(url2).query ?? []).map((entry) => ({ key: entry.key, value: entry.value ?? "", enabled: true }));
|
|
12000
12668
|
if (!isObject4(value)) return { url: text2(value), params: inText(text2(value)) };
|
|
12001
|
-
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("");
|
|
12002
12672
|
const params = objects(field3(value, "query")).map((entry) => {
|
|
12003
12673
|
const enabled = field3(entry, "disabled") !== true;
|
|
12004
|
-
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
|
+
};
|
|
12005
12679
|
});
|
|
12006
|
-
const off = params.filter((row) => !row.enabled).map((row) => row.key);
|
|
12007
|
-
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.` });
|
|
12008
12680
|
const host = field3(value, "host");
|
|
12009
|
-
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"))) };
|
|
12010
12683
|
const variables2 = objects(field3(value, "variable"));
|
|
12011
12684
|
const replaced = [];
|
|
12012
12685
|
const named2 = [];
|
|
@@ -12023,11 +12696,21 @@ function address(value, item, warnings) {
|
|
|
12023
12696
|
const protocol = field3(value, "protocol");
|
|
12024
12697
|
const port = field3(value, "port");
|
|
12025
12698
|
const path = field3(value, "path");
|
|
12026
|
-
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
|
+
);
|
|
12027
12702
|
const hash = field3(value, "hash");
|
|
12028
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)}`);
|
|
12029
|
-
if (replaced.length)
|
|
12030
|
-
|
|
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
|
+
});
|
|
12031
12714
|
return { url, params };
|
|
12032
12715
|
}
|
|
12033
12716
|
function body(value, item, warnings) {
|
|
@@ -12047,11 +12730,19 @@ function body(value, item, warnings) {
|
|
|
12047
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")) };
|
|
12048
12731
|
})
|
|
12049
12732
|
},
|
|
12050
|
-
...isObject4(field3(value, "file")) && {
|
|
12733
|
+
...isObject4(field3(value, "file")) && {
|
|
12734
|
+
binary: { fileId: "", fileName: file(field3(field3(value, "file"), "src")) }
|
|
12735
|
+
},
|
|
12051
12736
|
// Postman keeps the variables as text, as the app does.
|
|
12052
|
-
...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
|
+
}
|
|
12053
12743
|
};
|
|
12054
|
-
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." });
|
|
12055
12746
|
if (field3(value, "disabled") === true) return { mode: "none", ...kept };
|
|
12056
12747
|
switch (field3(value, "mode")) {
|
|
12057
12748
|
case "raw":
|
|
@@ -12069,7 +12760,10 @@ function body(value, item, warnings) {
|
|
|
12069
12760
|
case "none":
|
|
12070
12761
|
return { mode: "none", ...kept };
|
|
12071
12762
|
default:
|
|
12072
|
-
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
|
+
});
|
|
12073
12767
|
return { mode: "none", ...kept };
|
|
12074
12768
|
}
|
|
12075
12769
|
}
|
|
@@ -12114,7 +12808,10 @@ function postmanCollection(value) {
|
|
|
12114
12808
|
const method = text2(field3(source, "method") ?? "GET").toUpperCase();
|
|
12115
12809
|
examples += list2(field3(entry, "response")).length;
|
|
12116
12810
|
if (!url.trim() || !isHttpMethod(method)) {
|
|
12117
|
-
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
|
+
});
|
|
12118
12815
|
continue;
|
|
12119
12816
|
}
|
|
12120
12817
|
requests.push({
|
|
@@ -12139,7 +12836,11 @@ function postmanCollection(value) {
|
|
|
12139
12836
|
};
|
|
12140
12837
|
walk2(field3(value, "item"), void 0, 0);
|
|
12141
12838
|
const item = `collection "${name}"`;
|
|
12142
|
-
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
|
+
});
|
|
12143
12844
|
return {
|
|
12144
12845
|
payload: {
|
|
12145
12846
|
$id: COLLECTION_FORMAT,
|
|
@@ -12171,9 +12872,18 @@ function postmanEnvironment(value) {
|
|
|
12171
12872
|
meta: { id, createdAt: now, updatedAt: now },
|
|
12172
12873
|
name,
|
|
12173
12874
|
order: 0,
|
|
12174
|
-
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
|
+
}))
|
|
12175
12880
|
},
|
|
12176
|
-
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
|
+
] : []
|
|
12177
12887
|
};
|
|
12178
12888
|
}
|
|
12179
12889
|
|
|
@@ -12184,10 +12894,16 @@ function isInsomnia(value) {
|
|
|
12184
12894
|
return field3(value, "_type") === "export" || Object.hasOwn(value, "__export_format") || /^\w+\.insomnia(\.rest)?\/\d/.test(text2(field3(value, "type")));
|
|
12185
12895
|
}
|
|
12186
12896
|
var VARIABLE = /\{\{\s*(?:_\.|_(?=\[))?((?:\w[\w-]*|\[(?:'[^'\]]*'|"[^"\]]*")\])(?:\.\w[\w-]*|\[(?:'[^'\]]*'|"[^"\]]*")\])*)\s*\}\}/g;
|
|
12187
|
-
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
|
+
);
|
|
12188
12901
|
function foreign(value) {
|
|
12189
12902
|
const found = [];
|
|
12190
|
-
for (const [open, close] of [
|
|
12903
|
+
for (const [open, close] of [
|
|
12904
|
+
["{%", "%}"],
|
|
12905
|
+
["{{", "}}"]
|
|
12906
|
+
]) {
|
|
12191
12907
|
let at = value.indexOf(open);
|
|
12192
12908
|
while (at !== -1) {
|
|
12193
12909
|
const end = value.indexOf(close, at + 2);
|
|
@@ -12208,7 +12924,11 @@ function reader(item, warnings) {
|
|
|
12208
12924
|
return turned;
|
|
12209
12925
|
},
|
|
12210
12926
|
done: () => {
|
|
12211
|
-
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
|
+
});
|
|
12212
12932
|
}
|
|
12213
12933
|
};
|
|
12214
12934
|
}
|
|
@@ -12224,7 +12944,11 @@ function environment(name, vars) {
|
|
|
12224
12944
|
const now = Date.now();
|
|
12225
12945
|
return { id, meta: { id, createdAt: now, updatedAt: now }, name, order: 0, vars };
|
|
12226
12946
|
}
|
|
12227
|
-
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 !== "");
|
|
12228
12952
|
function auth2(value, said2) {
|
|
12229
12953
|
if (!isObject4(value) || typeof field3(value, "type") !== "string" || field3(value, "disabled") === true) return {};
|
|
12230
12954
|
const type = text2(field3(value, "type"));
|
|
@@ -12237,18 +12961,44 @@ function auth2(value, said2) {
|
|
|
12237
12961
|
case "bearer": {
|
|
12238
12962
|
const prefix = read("prefix").trim();
|
|
12239
12963
|
if (!prefix || prefix === "Bearer") return { auth: { type, bearer: { token: read("token") } } };
|
|
12240
|
-
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
|
+
};
|
|
12241
12974
|
}
|
|
12242
12975
|
case "apikey": {
|
|
12243
12976
|
const place = text2(field3(value, "addTo"));
|
|
12244
12977
|
if (place && place !== "header" && place !== "queryParams") return { auth: { type: `an API key in a ${place}` } };
|
|
12245
|
-
return {
|
|
12978
|
+
return {
|
|
12979
|
+
auth: {
|
|
12980
|
+
type,
|
|
12981
|
+
apikey: { key: read("key"), value: read("value"), in: place === "queryParams" ? "query" : "header" }
|
|
12982
|
+
}
|
|
12983
|
+
};
|
|
12246
12984
|
}
|
|
12247
12985
|
case "iam":
|
|
12248
|
-
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
|
+
};
|
|
12249
12998
|
case "oauth2": {
|
|
12250
12999
|
const grant = read("grantType");
|
|
12251
|
-
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" } } };
|
|
12252
13002
|
return {
|
|
12253
13003
|
auth: {
|
|
12254
13004
|
type,
|
|
@@ -12285,8 +13035,10 @@ function body2(value, said2) {
|
|
|
12285
13035
|
}
|
|
12286
13036
|
};
|
|
12287
13037
|
}
|
|
12288
|
-
if (type === "application/x-www-form-urlencoded" && Array.isArray(params))
|
|
12289
|
-
|
|
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") } } };
|
|
12290
13042
|
if (typeof field3(value, "text") !== "string" || !type && !field3(value, "text")) return {};
|
|
12291
13043
|
const language = /json|graphql/.test(type) ? "json" : type.includes("xml") ? "xml" : type.includes("html") ? "html" : type.includes("javascript") ? "javascript" : "text";
|
|
12292
13044
|
return { body: { mode: "raw", raw: said2(field3(value, "text")), options: { raw: { language } } } };
|
|
@@ -12295,7 +13047,11 @@ function builder(warnings) {
|
|
|
12295
13047
|
const folderVariables = [];
|
|
12296
13048
|
const scripts2 = (item, { pre, post }) => {
|
|
12297
13049
|
const kinds = [text2(pre).trim() && "pre-request", text2(post).trim() && "after-response"].filter(Boolean);
|
|
12298
|
-
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
|
+
});
|
|
12299
13055
|
};
|
|
12300
13056
|
const request = ({ source, name, description: description2, scripts: own3 }) => {
|
|
12301
13057
|
const item = `request "${name || "Request"}"`;
|
|
@@ -12307,7 +13063,11 @@ function builder(warnings) {
|
|
|
12307
13063
|
method: text2(field3(source, "method")),
|
|
12308
13064
|
description: description2,
|
|
12309
13065
|
// Insomnia holds the query's rows beside the address, and sends both.
|
|
12310
|
-
url: {
|
|
13066
|
+
url: {
|
|
13067
|
+
...url,
|
|
13068
|
+
query: [...url.query ?? [], ...pairs(field3(source, "parameters"), said2)],
|
|
13069
|
+
variable: pairs(field3(source, "pathParameters"), said2)
|
|
13070
|
+
},
|
|
12311
13071
|
header: pairs(field3(source, "headers"), said2),
|
|
12312
13072
|
...body2(field3(source, "body"), said2),
|
|
12313
13073
|
...auth2(field3(source, "authentication"), said2)
|
|
@@ -12323,7 +13083,14 @@ function builder(warnings) {
|
|
|
12323
13083
|
const variable = rowsOf(field3(source, "environment"), said2);
|
|
12324
13084
|
folderVariables.push(...variable.map(({ key }) => ({ item, key })));
|
|
12325
13085
|
const headers2 = pairs(field3(source, "headers"), said2).filter((header) => !header.disabled);
|
|
12326
|
-
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
|
+
});
|
|
12327
13094
|
scripts2(item, own3);
|
|
12328
13095
|
done();
|
|
12329
13096
|
return { name, description: description2, item: children, variable, ...auth2(field3(source, "authentication"), said2) };
|
|
@@ -12331,7 +13098,11 @@ function builder(warnings) {
|
|
|
12331
13098
|
const compare = (environments) => {
|
|
12332
13099
|
for (const { item, key } of folderVariables) {
|
|
12333
13100
|
const also = environments.find((entry) => entry.vars.some((row) => row.key === key));
|
|
12334
|
-
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
|
+
});
|
|
12335
13106
|
}
|
|
12336
13107
|
};
|
|
12337
13108
|
return { request, folder, compare };
|
|
@@ -12343,7 +13114,10 @@ function format5(value) {
|
|
|
12343
13114
|
const tree = field3(value, "collection");
|
|
12344
13115
|
const held = field3(value, "environments");
|
|
12345
13116
|
const type = text2(field3(value, "type"));
|
|
12346
|
-
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
|
+
);
|
|
12347
13121
|
const name = text2(field3(value, "name")).trim() || UNNAMED;
|
|
12348
13122
|
const item = Array.isArray(tree) ? `collection "${name}"` : `environment "${name}"`;
|
|
12349
13123
|
const { said: said2, done } = reader(item, warnings);
|
|
@@ -12352,15 +13126,26 @@ function format5(value) {
|
|
|
12352
13126
|
if (depth > MAX_DEPTH) throw tooDeep();
|
|
12353
13127
|
return objects(items).flatMap((source) => {
|
|
12354
13128
|
const own3 = isObject4(field3(source, "scripts")) ? field3(source, "scripts") : {};
|
|
12355
|
-
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
|
+
};
|
|
12356
13135
|
if (Array.isArray(field3(source, "children"))) return [folder(entry, level(field3(source, "children"), depth + 1))];
|
|
12357
13136
|
if (typeof field3(source, "method") === "string") return [request(entry)];
|
|
12358
|
-
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
|
+
});
|
|
12359
13141
|
return [];
|
|
12360
13142
|
});
|
|
12361
13143
|
};
|
|
12362
13144
|
const base = isObject4(held) ? rowsOf(field3(held, "data"), said2) : [];
|
|
12363
|
-
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
|
+
}));
|
|
12364
13149
|
const spec = field3(value, "spec");
|
|
12365
13150
|
const contents = isObject4(spec) ? field3(spec, "contents") : void 0;
|
|
12366
13151
|
const left = [
|
|
@@ -12369,13 +13154,28 @@ function format5(value) {
|
|
|
12369
13154
|
objects(field3(value, "testSuites")).length > 0 && "unit tests",
|
|
12370
13155
|
isObject4(spec) && (typeof field3(spec, "file") === "string" || isObject4(contents) && Object.keys(contents).length > 0) && "API specification"
|
|
12371
13156
|
].filter((kind) => typeof kind === "string");
|
|
12372
|
-
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
|
+
});
|
|
12373
13162
|
let result;
|
|
12374
13163
|
if (Array.isArray(tree)) {
|
|
12375
|
-
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
|
+
};
|
|
12376
13169
|
} else {
|
|
12377
|
-
const merged = (own3) => [
|
|
12378
|
-
|
|
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
|
+
};
|
|
12379
13179
|
}
|
|
12380
13180
|
done();
|
|
12381
13181
|
compare(result.environments);
|
|
@@ -12384,7 +13184,9 @@ function format5(value) {
|
|
|
12384
13184
|
function format4(value) {
|
|
12385
13185
|
const version = field3(value, "__export_format");
|
|
12386
13186
|
if (version !== 4) {
|
|
12387
|
-
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
|
+
);
|
|
12388
13190
|
}
|
|
12389
13191
|
const warnings = [];
|
|
12390
13192
|
const { request, folder, compare } = builder(warnings);
|
|
@@ -12405,7 +13207,12 @@ function format4(value) {
|
|
|
12405
13207
|
return (parent === "" ? [] : children.get(parent) ?? []).flatMap((source) => {
|
|
12406
13208
|
if (placed.has(source) || kind(source) !== "request" && kind(source) !== "request_group") return [];
|
|
12407
13209
|
placed.add(source);
|
|
12408
|
-
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
|
+
};
|
|
12409
13216
|
return [kind(source) === "request_group" ? folder(entry, level(id(source), depth + 1)) : request(entry)];
|
|
12410
13217
|
});
|
|
12411
13218
|
};
|
|
@@ -12413,7 +13220,11 @@ function format4(value) {
|
|
|
12413
13220
|
const collections = workspaces.map((workspace) => {
|
|
12414
13221
|
const name = text2(field3(workspace, "name")).trim() || UNNAMED;
|
|
12415
13222
|
const { said: said2, done } = reader(`collection "${name}"`, warnings);
|
|
12416
|
-
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
|
+
};
|
|
12417
13228
|
done();
|
|
12418
13229
|
return collection;
|
|
12419
13230
|
});
|
|
@@ -12426,16 +13237,38 @@ function format4(value) {
|
|
|
12426
13237
|
done();
|
|
12427
13238
|
return made;
|
|
12428
13239
|
});
|
|
12429
|
-
const known = new Set(
|
|
13240
|
+
const known = new Set(
|
|
13241
|
+
resources.filter((resource) => kind(resource) === "workspace" || kind(resource) === "request_group").map(id)
|
|
13242
|
+
);
|
|
12430
13243
|
const loose = [...children.keys()].filter((parent) => parent !== "" && !known.has(parent)).flatMap((parent) => level(parent, 1));
|
|
12431
13244
|
if (loose.length) collections.push({ info: { name: UNNAMED, description: void 0 }, variable: [], item: loose });
|
|
12432
|
-
const unplaced = resources.filter(
|
|
12433
|
-
|
|
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
|
+
});
|
|
12434
13253
|
for (const resource of resources) {
|
|
12435
|
-
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
|
+
});
|
|
12436
13259
|
}
|
|
12437
|
-
const others = [
|
|
12438
|
-
|
|
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
|
+
});
|
|
12439
13272
|
compare(environments);
|
|
12440
13273
|
return { collections, environments, warnings };
|
|
12441
13274
|
}
|
|
@@ -12481,13 +13314,19 @@ function openApiCollection(root) {
|
|
|
12481
13314
|
const swagger = field3(root, "swagger") !== void 0;
|
|
12482
13315
|
const version = text2(field3(root, swagger ? "swagger" : "openapi")).slice(0, 20);
|
|
12483
13316
|
if (swagger ? !/^2(\.0)?$/.test(version) : !/^3(\.|$)/.test(version)) {
|
|
12484
|
-
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
|
+
);
|
|
12485
13320
|
}
|
|
12486
13321
|
const warnings = [];
|
|
12487
13322
|
const info = field3(root, "info");
|
|
12488
13323
|
const name = text2(isObject4(info) ? field3(info, "title") : "").trim() || UNNAMED2;
|
|
12489
13324
|
const top = `collection "${name}"`;
|
|
12490
|
-
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
|
+
});
|
|
12491
13330
|
let item = top;
|
|
12492
13331
|
const unread = /* @__PURE__ */ new Map();
|
|
12493
13332
|
const note = (kind, ref) => {
|
|
@@ -12536,7 +13375,8 @@ function openApiCollection(root) {
|
|
|
12536
13375
|
const of2 = (raw, depth) => {
|
|
12537
13376
|
const schema = resolve(raw);
|
|
12538
13377
|
if (!isObject4(schema) || left-- <= 0 || depth > MAX_DEPTH || open.has(schema)) return null;
|
|
12539
|
-
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);
|
|
12540
13380
|
for (const key of ["examples", "enum"]) if (list3(field3(schema, key)).length) return list3(field3(schema, key))[0];
|
|
12541
13381
|
open.add(schema);
|
|
12542
13382
|
try {
|
|
@@ -12549,7 +13389,8 @@ function openApiCollection(root) {
|
|
|
12549
13389
|
});
|
|
12550
13390
|
parts.push(Object.fromEntries(sent.map((key) => [key, of2(field3(properties, key), depth + 1)])));
|
|
12551
13391
|
}
|
|
12552
|
-
if (parts.length)
|
|
13392
|
+
if (parts.length)
|
|
13393
|
+
return parts.some(isObject4) ? Object.fromEntries(parts.filter(isObject4).flatMap((part) => Object.entries(part))) : parts[0];
|
|
12553
13394
|
const choice = [...list3(field3(schema, "oneOf")), ...list3(field3(schema, "anyOf"))][0];
|
|
12554
13395
|
if (choice !== void 0) return of2(choice, depth + 1);
|
|
12555
13396
|
const types = field3(schema, "type");
|
|
@@ -12575,20 +13416,43 @@ function openApiCollection(root) {
|
|
|
12575
13416
|
const schema = resolve(raw);
|
|
12576
13417
|
return isObject4(schema) && (field3(schema, "type") === "file" || field3(schema, "format") === "binary");
|
|
12577
13418
|
};
|
|
12578
|
-
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) })) };
|
|
12579
13425
|
const bodyOf2 = (type, media) => {
|
|
12580
13426
|
const kind = type.toLowerCase();
|
|
12581
13427
|
const schema = resolve(field3(media, "schema"));
|
|
12582
13428
|
const value = example(media) ?? sample(schema, true);
|
|
12583
13429
|
if (kind.startsWith("multipart/") || kind === "application/x-www-form-urlencoded") {
|
|
12584
13430
|
const properties = isObject4(schema) ? field3(schema, "properties") : void 0;
|
|
12585
|
-
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
|
+
};
|
|
12586
13442
|
}
|
|
12587
|
-
if (kind === "application/octet-stream" || isFile(schema))
|
|
13443
|
+
if (kind === "application/octet-stream" || isFile(schema))
|
|
13444
|
+
return { body: { mode: "file", file: { src: "" } }, header: [] };
|
|
12588
13445
|
const language = /json|\*/.test(kind) ? "json" : kind.includes("xml") ? "xml" : kind.includes("html") ? "html" : kind.includes("javascript") ? "javascript" : "text";
|
|
12589
13446
|
const raw = typeof value === "string" ? value : language === "json" && value !== null ? JSON.stringify(value, null, 2) : "";
|
|
12590
|
-
if (!raw)
|
|
12591
|
-
|
|
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
|
+
};
|
|
12592
13456
|
};
|
|
12593
13457
|
const components = field3(root, "components");
|
|
12594
13458
|
const schemes = swagger ? field3(root, "securityDefinitions") : isObject4(components) ? field3(components, "securitySchemes") : void 0;
|
|
@@ -12596,16 +13460,22 @@ function openApiCollection(root) {
|
|
|
12596
13460
|
if (!Array.isArray(requirements)) return {};
|
|
12597
13461
|
const names = Object.keys(requirements.find(isObject4) ?? {});
|
|
12598
13462
|
if (!names.length) return { auth: { type: "noauth" } };
|
|
12599
|
-
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
|
+
});
|
|
12600
13468
|
const scheme = resolve(isObject4(schemes) ? field3(schemes, names[0]) : void 0);
|
|
12601
13469
|
if (!isObject4(scheme)) return { auth: { type: `${names[0].slice(0, 60)}, which the file does not describe` } };
|
|
12602
13470
|
const type = text2(field3(scheme, "type"));
|
|
12603
13471
|
const how = text2(field3(scheme, "scheme")).toLowerCase();
|
|
12604
|
-
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}}" } } };
|
|
12605
13474
|
if (type === "http" && how === "bearer") return { auth: { type: "bearer", bearer: { token: "{{bearerToken}}" } } };
|
|
12606
13475
|
if (type === "apiKey") {
|
|
12607
13476
|
const place = text2(field3(scheme, "in"));
|
|
12608
|
-
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"}` } };
|
|
12609
13479
|
return { auth: { type: "apikey", apikey: { key: text2(field3(scheme, "name")), value: "{{apiKey}}", in: place } } };
|
|
12610
13480
|
}
|
|
12611
13481
|
if (type === "oauth2") {
|
|
@@ -12615,7 +13485,15 @@ function openApiCollection(root) {
|
|
|
12615
13485
|
const client = flow("clientCredentials", "application");
|
|
12616
13486
|
const password = flow("password", "password");
|
|
12617
13487
|
const chosen = code ?? client ?? password;
|
|
12618
|
-
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
|
+
};
|
|
12619
13497
|
const asked = field3(requirements.find(isObject4) ?? {}, names[0]);
|
|
12620
13498
|
const scopes = field3(chosen, "scopes");
|
|
12621
13499
|
const scope = (Array.isArray(asked) && asked.length ? asked.map(text2) : isObject4(scopes) ? Object.keys(scopes) : []).join(" ");
|
|
@@ -12648,14 +13526,24 @@ function openApiCollection(root) {
|
|
|
12648
13526
|
return withoutEndSlash(filled);
|
|
12649
13527
|
};
|
|
12650
13528
|
const host = text2(field3(root, "host"));
|
|
12651
|
-
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")) ?? "";
|
|
12652
13532
|
if (!/^([a-z][a-z0-9+.-]*:\/\/|\{\{)/i.test(base)) {
|
|
12653
|
-
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
|
+
});
|
|
12654
13537
|
}
|
|
12655
13538
|
const others = list3(field3(root, "servers")).length - 1;
|
|
12656
|
-
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
|
+
});
|
|
12657
13544
|
const folders = /* @__PURE__ */ new Map();
|
|
12658
|
-
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: [] });
|
|
12659
13547
|
const loose = [];
|
|
12660
13548
|
const paths = field3(root, "paths");
|
|
12661
13549
|
for (const path of isObject4(paths) ? Object.keys(paths) : []) {
|
|
@@ -12668,7 +13556,8 @@ function openApiCollection(root) {
|
|
|
12668
13556
|
const requestName = text2(field3(operation, "summary")).trim() || text2(field3(operation, "operationId")).trim() || `${method.toUpperCase()} ${path}`;
|
|
12669
13557
|
item = `request "${requestName}"`;
|
|
12670
13558
|
const parameters = /* @__PURE__ */ new Map();
|
|
12671
|
-
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);
|
|
12672
13561
|
const query = [];
|
|
12673
13562
|
const header = [];
|
|
12674
13563
|
const cookies = [];
|
|
@@ -12685,7 +13574,12 @@ function openApiCollection(root) {
|
|
|
12685
13574
|
if (place === "formData") fields.push({ key, schema: parameter, value });
|
|
12686
13575
|
else if (place === "query") query.push({ key, value: said(value) });
|
|
12687
13576
|
else if (place === "cookie") cookies.push(`${key}=${said(value)}`);
|
|
12688
|
-
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
|
+
});
|
|
12689
13583
|
}
|
|
12690
13584
|
if (cookies.length) header.push({ key: "Cookie", value: cookies.join("; ") });
|
|
12691
13585
|
let body3;
|
|
@@ -12693,19 +13587,30 @@ function openApiCollection(root) {
|
|
|
12693
13587
|
const requestBody = resolve(field3(operation, "requestBody"));
|
|
12694
13588
|
const content = isObject4(requestBody) ? field3(requestBody, "content") : void 0;
|
|
12695
13589
|
if (fields.length) {
|
|
12696
|
-
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
|
+
);
|
|
12697
13594
|
} else if (inBody || isObject4(content)) {
|
|
12698
13595
|
const types = inBody ? consumes.length ? consumes : ["application/json"] : Object.keys(content).filter((type2) => isObject4(field3(content, type2)));
|
|
12699
13596
|
const type = types.find((entry) => /json/i.test(entry)) ?? types[0];
|
|
12700
13597
|
if (type !== void 0) {
|
|
12701
|
-
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
|
+
});
|
|
12702
13603
|
const made2 = bodyOf2(type, inBody ?? field3(content, type));
|
|
12703
13604
|
body3 = made2.body;
|
|
12704
13605
|
header.push(...made2.header);
|
|
12705
13606
|
}
|
|
12706
13607
|
}
|
|
12707
13608
|
const tags = list3(field3(operation, "tags")).map(text2).filter(Boolean);
|
|
12708
|
-
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
|
+
});
|
|
12709
13614
|
const own3 = server(field3(operation, "servers")) ?? server(field3(pathItem, "servers"));
|
|
12710
13615
|
const request = {
|
|
12711
13616
|
name: requestName,
|
|
@@ -12713,7 +13618,11 @@ function openApiCollection(root) {
|
|
|
12713
13618
|
method: method.toUpperCase(),
|
|
12714
13619
|
description: field3(operation, "description"),
|
|
12715
13620
|
// A `{name}` of the path is the variable `{{name}}`, which the composer asks for before it sends.
|
|
12716
|
-
url: {
|
|
13621
|
+
url: {
|
|
13622
|
+
host: [own3 ?? "{{baseUrl}}"],
|
|
13623
|
+
path: path.replace(/^\//, "").split("/").map((part) => part.replace(/\{([^{}]+)\}/g, "{{$1}}")),
|
|
13624
|
+
query
|
|
13625
|
+
},
|
|
12717
13626
|
header,
|
|
12718
13627
|
...body3 && { body: body3 },
|
|
12719
13628
|
...auth3(field3(operation, "security"))
|
|
@@ -12725,48 +13634,46 @@ function openApiCollection(root) {
|
|
|
12725
13634
|
}
|
|
12726
13635
|
}
|
|
12727
13636
|
item = top;
|
|
12728
|
-
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
|
+
};
|
|
12729
13642
|
const tagged = [...folders].filter(([, folder]) => folder.item.length).map(([folderName, folder]) => ({ name: folderName, description: folder.description, item: folder.item }));
|
|
12730
|
-
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.");
|
|
12731
13645
|
for (const [where, { outside, missing }] of unread) {
|
|
12732
|
-
if (outside.size)
|
|
12733
|
-
|
|
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
|
+
});
|
|
12734
13656
|
}
|
|
12735
13657
|
return { collection: { ...made, item: [...tagged, ...loose] }, warnings };
|
|
12736
13658
|
}
|
|
12737
13659
|
|
|
12738
|
-
// ../core/src/import/script-scan.ts
|
|
12739
|
-
var START = String.raw`(?<![\w$.])`;
|
|
12740
|
-
var escape = (text3) => text3.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
12741
|
-
var member = (name) => ({ api: name, finds: [[name, new RegExp(`${START}${escape(name)}(?![\\w$])`)]] });
|
|
12742
|
-
var required = (module) => [`require('${module}')`, new RegExp(`${START}require\\(\\s*["']${escape(module)}["']\\s*\\)`)];
|
|
12743
|
-
var called = (name) => [name, new RegExp(`${START}${escape(name)}\\s*\\(`)];
|
|
12744
|
-
var used = (name) => [name, new RegExp(`${START}${escape(name)}\\s*[.(]`)];
|
|
12745
|
-
var SCRIPT_SCAN = [
|
|
12746
|
-
...["pm.cookies.get", "pm.cookies.has", "pm.cookies.toObject", "pm.cookies.jar", "pm.visualizer.set", "pm.vault.get", "pm.require", "postman.getResponseCookie"].map(member),
|
|
12747
|
-
{ api: "legacy library globals (_, CryptoJS, tv4, cheerio, xml2Json, Backbone)", finds: ["tv4", "cheerio", "xml2Json", "Backbone"].map(used) },
|
|
12748
|
-
{ api: "clearTimeout, setInterval, clearInterval", finds: ["clearTimeout", "setInterval", "clearInterval"].map(called) },
|
|
12749
|
-
...["ajv", "cheerio", "csv-parse/lib/sync", "postman-collection", "tv4", "xml2js"].map((module) => ({ api: `require('${module}')`, finds: [required(module)] })),
|
|
12750
|
-
{
|
|
12751
|
-
api: "require of a Node module (buffer, events, path, querystring, stream, string_decoder, timers, url, util)",
|
|
12752
|
-
finds: ["buffer", "events", "path", "querystring", "stream", "string_decoder", "timers", "url", "util"].map(required)
|
|
12753
|
-
}
|
|
12754
|
-
];
|
|
12755
|
-
function unsupportedApis(script) {
|
|
12756
|
-
return SCRIPT_SCAN.flatMap((entry) => entry.finds).filter(([, pattern]) => pattern.test(script)).map(([label]) => label);
|
|
12757
|
-
}
|
|
12758
|
-
|
|
12759
13660
|
// ../core/src/import/yaml.ts
|
|
12760
13661
|
var import_yaml = __toESM(require_dist(), 1);
|
|
12761
13662
|
var MAX_YAML_ALIASES = 100;
|
|
12762
13663
|
var MAX_YAML_CHARS = 1024 * 1024;
|
|
12763
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.`;
|
|
12764
|
-
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.";
|
|
12765
13666
|
var NOT_READABLE = "The file is neither JSON nor YAML that can be read, so it is not a file Wayfarer can import.";
|
|
12766
13667
|
function parseYaml(text3) {
|
|
12767
13668
|
if (text3.length > MAX_YAML_CHARS) throw new ImportError(YAML_TOO_LARGE);
|
|
12768
13669
|
try {
|
|
12769
|
-
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
|
+
});
|
|
12770
13677
|
} catch (error) {
|
|
12771
13678
|
throw new ImportError(error instanceof Error && /alias count/i.test(error.message) ? YAML_ALIASES : NOT_READABLE);
|
|
12772
13679
|
}
|
|
@@ -12806,7 +13713,12 @@ function scriptWarnings(item, scripts2) {
|
|
|
12806
13713
|
const found = unsupportedApis(scripts2[kind]);
|
|
12807
13714
|
if (!found.length) return [];
|
|
12808
13715
|
const names = found.length > 1 ? `${found.slice(0, -1).join(", ")} and ${found.at(-1)}` : found[0];
|
|
12809
|
-
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
|
+
];
|
|
12810
13722
|
});
|
|
12811
13723
|
}
|
|
12812
13724
|
function inheritedScriptWarnings(payload) {
|
|
@@ -12821,9 +13733,13 @@ function curlToRequest(text3) {
|
|
|
12821
13733
|
const parsed2 = parseCurl(text3);
|
|
12822
13734
|
const { content, warnings } = curlRequest(parsed2);
|
|
12823
13735
|
const issues = validateRequestContent(content, "request");
|
|
12824
|
-
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);
|
|
12825
13737
|
const address2 = URL.parse(parsed2.request.url);
|
|
12826
|
-
return {
|
|
13738
|
+
return {
|
|
13739
|
+
name: `${content.method} ${address2 ? address2.host + address2.pathname : parsed2.request.url}`,
|
|
13740
|
+
content,
|
|
13741
|
+
warnings
|
|
13742
|
+
};
|
|
12827
13743
|
}
|
|
12828
13744
|
function built(format, name, requests) {
|
|
12829
13745
|
const now = Date.now();
|
|
@@ -12832,7 +13748,15 @@ function built(format, name, requests) {
|
|
|
12832
13748
|
const payload = {
|
|
12833
13749
|
$id: COLLECTION_FORMAT,
|
|
12834
13750
|
meta: top,
|
|
12835
|
-
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
|
+
},
|
|
12836
13760
|
folders: [],
|
|
12837
13761
|
requests: requests.map(({ name: requestName, content }, order) => {
|
|
12838
13762
|
const own3 = meta();
|
|
@@ -12844,7 +13768,9 @@ function built(format, name, requests) {
|
|
|
12844
13768
|
format,
|
|
12845
13769
|
formatName: IMPORT_FORMATS[format],
|
|
12846
13770
|
counts: { collections: 1, folders: 0, requests: requests.length, environments: 0 },
|
|
12847
|
-
warnings: requests.flatMap(
|
|
13771
|
+
warnings: requests.flatMap(
|
|
13772
|
+
({ name: requestName, warnings }) => warnings.map((message) => ({ item: `request "${requestName}"`, message }))
|
|
13773
|
+
)
|
|
12848
13774
|
},
|
|
12849
13775
|
collections: [added(payload)],
|
|
12850
13776
|
environments: []
|
|
@@ -12852,10 +13778,57 @@ function built(format, name, requests) {
|
|
|
12852
13778
|
}
|
|
12853
13779
|
function added(payload) {
|
|
12854
13780
|
const { payload: checked, plan, errors } = importCollection(payload, { duplicateAsNew: true });
|
|
12855
|
-
if (!checked || !plan) throw new ImportError(
|
|
13781
|
+
if (!checked || !plan) throw new ImportError("This file holds no request Wayfarer can read.", errors ?? []);
|
|
12856
13782
|
return { payload: checked, plan };
|
|
12857
13783
|
}
|
|
12858
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) {
|
|
12859
13832
|
if (isOversizedImport(text3)) throw new ImportError(IMPORT_TOO_LARGE);
|
|
12860
13833
|
if (text3.startsWith("\uFEFF")) text3 = text3.slice(1);
|
|
12861
13834
|
if (isCurlCommand(text3)) {
|
|
@@ -12867,8 +13840,12 @@ function importText(text3, options = {}) {
|
|
|
12867
13840
|
if (!json.ok && (typeof value !== "object" || value === null)) throw new ImportError(NOT_READABLE);
|
|
12868
13841
|
const format = detectFormat(value);
|
|
12869
13842
|
if (!format) {
|
|
12870
|
-
if (of(idOf(value), "wayfarer/workspace"))
|
|
12871
|
-
|
|
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
|
+
);
|
|
12872
13849
|
throw new ImportError(UNKNOWN);
|
|
12873
13850
|
}
|
|
12874
13851
|
const report = (counts2, warnings) => ({
|
|
@@ -12878,7 +13855,10 @@ function importText(text3, options = {}) {
|
|
|
12878
13855
|
warnings
|
|
12879
13856
|
});
|
|
12880
13857
|
if (format === "har") {
|
|
12881
|
-
const requests = parseHar(value).map(({ name, request, warnings }) => ({
|
|
13858
|
+
const requests = parseHar(value).map(({ name, request, warnings }) => ({
|
|
13859
|
+
name,
|
|
13860
|
+
...curlRequest({ request, warnings })
|
|
13861
|
+
}));
|
|
12882
13862
|
return built("har", "Imported from HAR", requests);
|
|
12883
13863
|
}
|
|
12884
13864
|
if (format === "postman-collection" || format === "openapi") {
|
|
@@ -12886,7 +13866,11 @@ function importText(text3, options = {}) {
|
|
|
12886
13866
|
const { payload: payload2, warnings } = postmanCollection(source.collection);
|
|
12887
13867
|
const collection = added(payload2);
|
|
12888
13868
|
return {
|
|
12889
|
-
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
|
+
]),
|
|
12890
13874
|
collections: [collection],
|
|
12891
13875
|
environments: []
|
|
12892
13876
|
};
|
|
@@ -12899,10 +13883,15 @@ function importText(text3, options = {}) {
|
|
|
12899
13883
|
if (issues.length) throw new ImportError("The file's environments are not ones the app can hold.", issues);
|
|
12900
13884
|
const count = (of2) => collections.reduce((sum, { payload: payload2 }) => sum + of2(payload2).length, 0);
|
|
12901
13885
|
return {
|
|
12902
|
-
report: report(
|
|
12903
|
-
|
|
12904
|
-
|
|
12905
|
-
|
|
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
|
+
),
|
|
12906
13895
|
collections,
|
|
12907
13896
|
environments
|
|
12908
13897
|
};
|
|
@@ -12915,12 +13904,28 @@ function importText(text3, options = {}) {
|
|
|
12915
13904
|
}
|
|
12916
13905
|
if (format === "wayfarer-environments") {
|
|
12917
13906
|
const result = validateEnvironmentExport(value);
|
|
12918
|
-
if (!result.payload)
|
|
12919
|
-
|
|
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
|
+
};
|
|
12920
13917
|
}
|
|
12921
13918
|
const { payload, plan, errors } = importCollection(value, options);
|
|
12922
|
-
if (!payload || !plan)
|
|
12923
|
-
|
|
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
|
+
};
|
|
12924
13929
|
}
|
|
12925
13930
|
|
|
12926
13931
|
// ../core/src/run/report.ts
|
|
@@ -12933,10 +13938,21 @@ var counts = (results) => ({
|
|
|
12933
13938
|
errors: results.filter((result) => result.error !== void 0).length
|
|
12934
13939
|
});
|
|
12935
13940
|
function runReportJson(report, info) {
|
|
12936
|
-
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
|
+
};
|
|
12937
13950
|
}
|
|
12938
13951
|
function xml(text3) {
|
|
12939
|
-
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
|
+
);
|
|
12940
13956
|
}
|
|
12941
13957
|
var seconds = (ms) => ((ms ?? 0) / 1e3).toFixed(3);
|
|
12942
13958
|
function junitXml(report, info) {
|
|
@@ -12947,12 +13963,18 @@ function junitXml(report, info) {
|
|
|
12947
13963
|
<failure type="AssertionFailure" message="${xml(test.error ?? "")}"/>
|
|
12948
13964
|
</testcase>`
|
|
12949
13965
|
);
|
|
12950
|
-
if (result.error !== void 0)
|
|
13966
|
+
if (result.error !== void 0)
|
|
13967
|
+
cases.push(
|
|
13968
|
+
` <testcase name="${classname}" classname="${classname}">
|
|
12951
13969
|
<error type="Error" message="${xml(result.error)}"/>
|
|
12952
|
-
</testcase>`
|
|
12953
|
-
|
|
13970
|
+
</testcase>`
|
|
13971
|
+
);
|
|
13972
|
+
if (result.skipped)
|
|
13973
|
+
cases.push(
|
|
13974
|
+
` <testcase name="${classname}" classname="${classname}">
|
|
12954
13975
|
<skipped message="pm.execution.skipRequest()"/>
|
|
12955
|
-
</testcase>`
|
|
13976
|
+
</testcase>`
|
|
13977
|
+
);
|
|
12956
13978
|
const totals2 = counts([result]);
|
|
12957
13979
|
const name = xml(report.iterations > 1 ? `${result.name} (iteration ${result.iteration + 1})` : result.name);
|
|
12958
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)}"`;
|
|
@@ -12970,6 +13992,97 @@ ${cases.join("\n")}
|
|
|
12970
13992
|
""
|
|
12971
13993
|
].join("\n");
|
|
12972
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
|
+
}
|
|
12973
14086
|
|
|
12974
14087
|
// src/run.ts
|
|
12975
14088
|
import variant from "@jitl/quickjs-wasmfile-release-sync";
|
|
@@ -13014,10 +14127,16 @@ async function runCollection(input, options) {
|
|
|
13014
14127
|
const transport = new FetchTransport();
|
|
13015
14128
|
const ports = {
|
|
13016
14129
|
runScript: async (script, env, response, { send: send2, ...extras }) => {
|
|
13017
|
-
const result = await runScript(
|
|
13018
|
-
|
|
13019
|
-
|
|
13020
|
-
|
|
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
|
+
);
|
|
13021
14140
|
if (result.limit) engine = engines();
|
|
13022
14141
|
return result;
|
|
13023
14142
|
},
|
|
@@ -13032,7 +14151,9 @@ async function runCollection(input, options) {
|
|
|
13032
14151
|
environment: () => environment2 && {
|
|
13033
14152
|
name: environment2.name,
|
|
13034
14153
|
vars: environment2.vars,
|
|
13035
|
-
change: (changes) => Promise.resolve(
|
|
14154
|
+
change: (changes) => Promise.resolve(
|
|
14155
|
+
environment2 && (environment2 = { ...environment2, vars: applyVariableChanges(environment2.vars, changes) })
|
|
14156
|
+
)
|
|
13036
14157
|
},
|
|
13037
14158
|
globals: () => globals,
|
|
13038
14159
|
changeGlobals: (changes) => Promise.resolve(globals = applyVariableChanges(globals, changes)),
|
|
@@ -13054,7 +14175,13 @@ async function runCollection(input, options) {
|
|
|
13054
14175
|
const send = async (doc, iteration, signal) => {
|
|
13055
14176
|
const ancestors = ancestorsOf(source.collection, source.folders, doc.folderId);
|
|
13056
14177
|
const folder = folderChain(source.folders, doc.folderId).flatMap((entry) => entry.variables);
|
|
13057
|
-
const scopes = () => ({
|
|
14178
|
+
const scopes = () => ({
|
|
14179
|
+
environment: environment2?.vars,
|
|
14180
|
+
folder,
|
|
14181
|
+
collection: collectionVariables,
|
|
14182
|
+
global: globals,
|
|
14183
|
+
data: iteration.data
|
|
14184
|
+
});
|
|
13058
14185
|
const { outcome } = await runOutcome(
|
|
13059
14186
|
() => exchange(
|
|
13060
14187
|
{
|
|
@@ -13067,7 +14194,10 @@ async function runCollection(input, options) {
|
|
|
13067
14194
|
scopes,
|
|
13068
14195
|
ancestors,
|
|
13069
14196
|
readSecrets: async (ids) => {
|
|
13070
|
-
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
|
+
);
|
|
13071
14201
|
const found = /* @__PURE__ */ new Map();
|
|
13072
14202
|
for (const id of ids) {
|
|
13073
14203
|
const value = await readSecret(id);
|
|
@@ -13075,12 +14205,27 @@ async function runCollection(input, options) {
|
|
|
13075
14205
|
}
|
|
13076
14206
|
return found;
|
|
13077
14207
|
},
|
|
13078
|
-
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
|
+
),
|
|
13079
14213
|
blockUnresolved: true,
|
|
13080
|
-
composed: {
|
|
14214
|
+
composed: {
|
|
14215
|
+
url: doc.url,
|
|
14216
|
+
scopes: { ...baseline, folder, data: iteration.data },
|
|
14217
|
+
accepted: acceptedHosts
|
|
14218
|
+
}
|
|
13081
14219
|
}),
|
|
13082
|
-
info: {
|
|
13083
|
-
|
|
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
|
+
},
|
|
13084
14229
|
inherited: ancestors,
|
|
13085
14230
|
folderVariables: () => folder,
|
|
13086
14231
|
signal,
|
|
@@ -13121,9 +14266,10 @@ Options:
|
|
|
13121
14266
|
-n, --iterations <n> How many iterations (default: one per row of the
|
|
13122
14267
|
data file, or 1).
|
|
13123
14268
|
--collection <name> Which collection of a file that holds several.
|
|
13124
|
-
--reporter <list> cli, json, junit, comma-separated (default
|
|
13125
|
-
json writes wayfarer-run.json
|
|
13126
|
-
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.
|
|
13127
14273
|
--out <dir> Where the reports are written (default: here).
|
|
13128
14274
|
--bail Stop at the first failure.
|
|
13129
14275
|
--timeout <ms> Give up on a request after this long (default: no
|
|
@@ -13142,46 +14288,64 @@ Exit codes: 0 every request answered and every test passed; 1 a test failed
|
|
|
13142
14288
|
or a request got no response; 2 nothing was run (see the message).`;
|
|
13143
14289
|
var Refusal = class extends Error {
|
|
13144
14290
|
};
|
|
13145
|
-
async function
|
|
14291
|
+
async function readText2(file, what) {
|
|
13146
14292
|
try {
|
|
13147
14293
|
return await readFile(file, "utf8");
|
|
13148
14294
|
} catch (error) {
|
|
13149
|
-
throw new Refusal(
|
|
14295
|
+
throw new Refusal(
|
|
14296
|
+
`The ${what} ${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
14297
|
+
);
|
|
13150
14298
|
}
|
|
13151
14299
|
}
|
|
13152
14300
|
function pick(list4, name, what, file) {
|
|
13153
14301
|
if (name !== void 0) {
|
|
13154
14302
|
const found = list4.find((entry) => entry.name === name);
|
|
13155
|
-
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
|
+
);
|
|
13156
14307
|
return found;
|
|
13157
14308
|
}
|
|
13158
|
-
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
|
+
);
|
|
13159
14313
|
const [only] = list4;
|
|
13160
14314
|
if (!only) throw new Refusal(`${file} holds no ${what}.`);
|
|
13161
14315
|
return only;
|
|
13162
14316
|
}
|
|
13163
14317
|
async function openVault(record, secrets, file) {
|
|
13164
14318
|
const passphrase = process.env["WAYFARER_VAULT_PASSPHRASE"];
|
|
13165
|
-
if (passphrase === void 0)
|
|
14319
|
+
if (passphrase === void 0)
|
|
14320
|
+
throw new Refusal(`${file} holds a vault: set WAYFARER_VAULT_PASSPHRASE to its passphrase.`);
|
|
13166
14321
|
const dek = await unwrapDek(record, passphrase);
|
|
13167
14322
|
if (!dek) throw new Refusal(`WAYFARER_VAULT_PASSPHRASE is not the passphrase of the vault in ${file}.`);
|
|
13168
14323
|
return { dek, secrets: new Map(secrets.map((secret) => [secret.id, secret.envelope])) };
|
|
13169
14324
|
}
|
|
13170
14325
|
async function readSource(file, collection) {
|
|
13171
|
-
const text3 = await
|
|
14326
|
+
const text3 = await readText2(file, "file");
|
|
13172
14327
|
const parsed2 = parseJson(text3);
|
|
13173
14328
|
const id = parsed2.ok && typeof parsed2.value === "object" && parsed2.value !== null && "$id" in parsed2.value ? String(parsed2.value.$id) : "";
|
|
13174
14329
|
if (id !== "wayfarer/workspace" && !id.startsWith("wayfarer/workspace/")) {
|
|
13175
14330
|
try {
|
|
13176
14331
|
const { collections } = importText(text3);
|
|
13177
|
-
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
|
+
};
|
|
13178
14341
|
} catch (error) {
|
|
13179
14342
|
if (error instanceof Error && error.name === "ImportError") throw new Refusal(`${file}: ${error.message}`);
|
|
13180
14343
|
throw error;
|
|
13181
14344
|
}
|
|
13182
14345
|
}
|
|
13183
14346
|
const errors = validateWorkspaceFile(parsed2.ok ? parsed2.value : void 0);
|
|
13184
|
-
if (errors.length)
|
|
14347
|
+
if (errors.length)
|
|
14348
|
+
throw new Refusal(`${file} is not a workspace backup this version reads:
|
|
13185
14349
|
${errors.join("\n ")}`);
|
|
13186
14350
|
const { stores } = parsed2.ok ? parsed2.value : {};
|
|
13187
14351
|
const chosen = pick(stores.collections, collection, "collection", file);
|
|
@@ -13201,12 +14365,14 @@ async function readSource(file, collection) {
|
|
|
13201
14365
|
...environment2 && { environment: { name: environment2.name, vars: environment2.vars } },
|
|
13202
14366
|
globals: meta(META_GLOBALS_KEY)?.["variables"] ?? [],
|
|
13203
14367
|
// A backup's secrets are useful only with the passphrase: without one, a request that needs a secret says so.
|
|
13204
|
-
...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
|
+
}
|
|
13205
14371
|
};
|
|
13206
14372
|
}
|
|
13207
14373
|
async function readEnvironment(file) {
|
|
13208
14374
|
try {
|
|
13209
|
-
const { environments } = importText(await
|
|
14375
|
+
const { environments } = importText(await readText2(file, "environment"));
|
|
13210
14376
|
const { name, vars } = pick(environments, void 0, "environment", file);
|
|
13211
14377
|
return { name, vars };
|
|
13212
14378
|
} catch (error) {
|
|
@@ -13215,9 +14381,12 @@ async function readEnvironment(file) {
|
|
|
13215
14381
|
}
|
|
13216
14382
|
}
|
|
13217
14383
|
async function readVaultFile(file) {
|
|
13218
|
-
const parsed2 = parseJson(await
|
|
14384
|
+
const parsed2 = parseJson(await readText2(file, "vault file"));
|
|
13219
14385
|
const issues = parsed2.ok ? validateVaultFile(parsed2.value) : [{ path: "", message: "not JSON" }];
|
|
13220
|
-
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
|
+
);
|
|
13221
14390
|
const { vault, secrets } = parsed2.ok ? parsed2.value : {};
|
|
13222
14391
|
return openVault(vault, secrets, file);
|
|
13223
14392
|
}
|
|
@@ -13228,21 +14397,22 @@ function scriptHolders(source) {
|
|
|
13228
14397
|
...source.requests.filter((request) => scriptsOf(request).length).map((request) => `the request "${request.name}"`)
|
|
13229
14398
|
];
|
|
13230
14399
|
}
|
|
13231
|
-
function line(result, iterations) {
|
|
14400
|
+
function line(result, iterations, said2) {
|
|
13232
14401
|
const mark = runFailed(result) ? "\u2717" : result.skipped ? "-" : "\u2713";
|
|
13233
14402
|
const where = iterations > 1 ? ` (iteration ${result.iteration + 1})` : "";
|
|
13234
14403
|
const status = result.skipped ? "skipped" : result.status ? `${result.status} ${result.statusText ?? ""}`.trim() : "no response";
|
|
13235
14404
|
const tests2 = result.tests.map((test) => `
|
|
13236
14405
|
${test.passed ? "\u2713" : "\u2717"} ${test.name}${test.error ? `: ${test.error}` : ""}`).join("");
|
|
13237
|
-
const
|
|
13238
|
-
|
|
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}`;
|
|
13239
14410
|
return `${mark} ${result.name}${where}: ${status}${result.durationMs !== void 0 ? `, ${result.durationMs} ms` : ""}${tests2}${error}`;
|
|
13240
14411
|
}
|
|
13241
14412
|
function summary(report) {
|
|
13242
|
-
const
|
|
13243
|
-
|
|
13244
|
-
|
|
13245
|
-
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("")}`;
|
|
13246
14416
|
}
|
|
13247
14417
|
async function main(argv) {
|
|
13248
14418
|
const { values, positionals } = parseArgs({
|
|
@@ -13265,7 +14435,7 @@ async function main(argv) {
|
|
|
13265
14435
|
}
|
|
13266
14436
|
});
|
|
13267
14437
|
if (values.version) {
|
|
13268
|
-
console.log("2.0
|
|
14438
|
+
console.log("2.1.0");
|
|
13269
14439
|
return 0;
|
|
13270
14440
|
}
|
|
13271
14441
|
const [command, file, ...rest] = positionals;
|
|
@@ -13273,11 +14443,14 @@ async function main(argv) {
|
|
|
13273
14443
|
console.log(USAGE);
|
|
13274
14444
|
return values.help ? 0 : 2;
|
|
13275
14445
|
}
|
|
13276
|
-
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(" ")}
|
|
13277
14448
|
|
|
13278
14449
|
${USAGE}`);
|
|
13279
14450
|
const reporters = new Set(values.reporter.split(",").map((name) => name.trim()));
|
|
13280
|
-
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.`);
|
|
13281
14454
|
const number = (text3, flag2) => {
|
|
13282
14455
|
if (text3 === void 0) return void 0;
|
|
13283
14456
|
if (!/^\d+$/.test(text3)) throw new Refusal(`--${flag2} takes a whole number, not "${text3}".`);
|
|
@@ -13289,43 +14462,56 @@ ${USAGE}`);
|
|
|
13289
14462
|
const read = await readSource(file, values.collection);
|
|
13290
14463
|
const holders = scriptHolders(read.source);
|
|
13291
14464
|
if (holders.length && !values["allow-scripts"]) {
|
|
13292
|
-
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
|
+
);
|
|
13293
14468
|
}
|
|
13294
14469
|
const environment2 = values.environment === void 0 ? read.environment : await readEnvironment(values.environment);
|
|
13295
14470
|
const vault = values["vault-file"] === void 0 ? read.vault : await readVaultFile(values["vault-file"]);
|
|
13296
14471
|
let data;
|
|
13297
14472
|
if (values.data !== void 0) {
|
|
13298
14473
|
try {
|
|
13299
|
-
data = parseDataFile(await
|
|
14474
|
+
data = parseDataFile(await readText2(values.data, "data file"), basename(values.data));
|
|
13300
14475
|
} catch (error) {
|
|
13301
|
-
if (error instanceof Error && error.name === "DataFileError")
|
|
14476
|
+
if (error instanceof Error && error.name === "DataFileError")
|
|
14477
|
+
throw new Refusal(`${values.data}: ${error.message}`);
|
|
13302
14478
|
throw error;
|
|
13303
14479
|
}
|
|
13304
14480
|
}
|
|
13305
14481
|
if (values.insecure) process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";
|
|
13306
14482
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13307
|
-
if (reporters.has("json") || reporters.has("junit")) {
|
|
14483
|
+
if (reporters.has("json") || reporters.has("junit") || reporters.has("html")) {
|
|
13308
14484
|
await mkdir(values.out, { recursive: true }).catch((error) => {
|
|
13309
14485
|
throw new Refusal(`--out ${values.out}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13310
14486
|
});
|
|
13311
14487
|
}
|
|
14488
|
+
const said2 = /* @__PURE__ */ new Set();
|
|
13312
14489
|
const report = await runCollection(
|
|
13313
|
-
{
|
|
14490
|
+
{
|
|
14491
|
+
source: read.source,
|
|
14492
|
+
...environment2 && { environment: environment2 },
|
|
14493
|
+
globals: read.globals,
|
|
14494
|
+
...vault && { vault },
|
|
14495
|
+
allowScripts: values["allow-scripts"],
|
|
14496
|
+
timeoutMs
|
|
14497
|
+
},
|
|
13314
14498
|
{
|
|
13315
14499
|
...iterations !== void 0 && { iterations },
|
|
13316
14500
|
...data && { data },
|
|
13317
14501
|
stopOnFailure: values.bail,
|
|
13318
14502
|
onResult: (result) => {
|
|
13319
|
-
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));
|
|
13320
14504
|
}
|
|
13321
14505
|
}
|
|
13322
14506
|
);
|
|
13323
14507
|
const info = { name: read.source.collection.name, startedAt };
|
|
13324
14508
|
if (reporters.has("cli")) console.log(`
|
|
13325
14509
|
${summary(report)}`);
|
|
13326
|
-
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)}
|
|
13327
14512
|
`);
|
|
13328
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));
|
|
13329
14515
|
return report.results.some(runFailed) ? 1 : 0;
|
|
13330
14516
|
}
|
|
13331
14517
|
main(process.argv.slice(2)).then(
|