wayfarer-cli 2.0.2 → 2.2.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 +15 -6
- package/dist/wayfarer-cli.mjs +1914 -613
- 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
|
}
|
|
@@ -7493,9 +7493,9 @@ function isTextType(mediaType) {
|
|
|
7493
7493
|
const subtype = type.slice(type.indexOf("/") + 1);
|
|
7494
7494
|
return type.startsWith("text/") || subtype === "xml" || subtype.startsWith("xml-") || subtype.endsWith("+xml") || TEXT_WORDS.some((word) => type.includes(word));
|
|
7495
7495
|
}
|
|
7496
|
-
function
|
|
7496
|
+
function readBody(bytes, contentType2) {
|
|
7497
7497
|
if (!bytes || bytes.byteLength === 0) {
|
|
7498
|
-
return null;
|
|
7498
|
+
return { value: null };
|
|
7499
7499
|
}
|
|
7500
7500
|
const type = (contentType2 ?? "").trim();
|
|
7501
7501
|
const mediaType = type.split(";")[0].trim();
|
|
@@ -7505,21 +7505,21 @@ function decodeResponseBody(bytes, contentType2) {
|
|
|
7505
7505
|
text3 = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
7506
7506
|
} catch (error) {
|
|
7507
7507
|
if (!(error instanceof TypeError)) throw error;
|
|
7508
|
-
return new BinaryBody(bytes, "");
|
|
7508
|
+
return { value: new BinaryBody(bytes, "") };
|
|
7509
7509
|
}
|
|
7510
7510
|
} else if (JSON_TYPE.test(mediaType) || isTextType(mediaType)) {
|
|
7511
7511
|
text3 = decodeText(bytes, charsetOf(type));
|
|
7512
7512
|
} else {
|
|
7513
|
-
return new BinaryBody(bytes, mediaType);
|
|
7513
|
+
return { value: new BinaryBody(bytes, mediaType) };
|
|
7514
7514
|
}
|
|
7515
7515
|
const trimmed = text3.trim();
|
|
7516
7516
|
if (JSON_TYPE.test(mediaType) || trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
7517
7517
|
const parsed2 = parseJson(text3);
|
|
7518
7518
|
if (parsed2.ok && parsed2.value !== null && (JSON_TYPE.test(mediaType) || typeof parsed2.value === "object")) {
|
|
7519
|
-
return parsed2.value;
|
|
7519
|
+
return { value: parsed2.value, text: text3 };
|
|
7520
7520
|
}
|
|
7521
7521
|
}
|
|
7522
|
-
return text3;
|
|
7522
|
+
return { value: text3, text: text3 };
|
|
7523
7523
|
}
|
|
7524
7524
|
function charsetOf(contentType2) {
|
|
7525
7525
|
const match = /;\s*charset\s*=\s*"?([^";\s]+)"?/i.exec(contentType2);
|
|
@@ -7533,12 +7533,12 @@ function decodeText(bytes, charset) {
|
|
|
7533
7533
|
return new TextDecoder("utf-8").decode(bytes);
|
|
7534
7534
|
}
|
|
7535
7535
|
}
|
|
7536
|
-
function
|
|
7536
|
+
function readEnvelope(envelope) {
|
|
7537
7537
|
const contentType2 = envelope.headers.find(([name]) => name.toLowerCase() === "content-type")?.[1];
|
|
7538
7538
|
if (envelope.body instanceof Blob) {
|
|
7539
|
-
return new BinaryBody(envelope.body, (contentType2 ?? "").split(";")[0].trim());
|
|
7539
|
+
return { value: new BinaryBody(envelope.body, (contentType2 ?? "").split(";")[0].trim()) };
|
|
7540
7540
|
}
|
|
7541
|
-
return
|
|
7541
|
+
return readBody(envelope.body, contentType2);
|
|
7542
7542
|
}
|
|
7543
7543
|
|
|
7544
7544
|
// ../core/src/id.ts
|
|
@@ -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;
|
|
@@ -7586,15 +7589,16 @@ function buildAuthHeaders(auth3) {
|
|
|
7586
7589
|
return { Authorization: `Bearer ${auth3.token}` };
|
|
7587
7590
|
}
|
|
7588
7591
|
if (auth3.type === "basic" && auth3.username) {
|
|
7589
|
-
|
|
7590
|
-
const encoded = btoa(String.fromCharCode(...bytes));
|
|
7591
|
-
return { Authorization: `Basic ${encoded}` };
|
|
7592
|
+
return { Authorization: basicCredentials(auth3.username, auth3.password) };
|
|
7592
7593
|
}
|
|
7593
7594
|
if (auth3.type === "apikey" && auth3.key && auth3.in === "header") {
|
|
7594
7595
|
return Object.fromEntries([[auth3.key, auth3.value]]);
|
|
7595
7596
|
}
|
|
7596
7597
|
return {};
|
|
7597
7598
|
}
|
|
7599
|
+
function basicCredentials(user, password) {
|
|
7600
|
+
return `Basic ${btoa(String.fromCharCode(...new TextEncoder().encode(`${user}:${password}`)))}`;
|
|
7601
|
+
}
|
|
7598
7602
|
function buildAuthQueryParam(auth3) {
|
|
7599
7603
|
if (auth3.type === "apikey" && auth3.key && auth3.in === "query") {
|
|
7600
7604
|
return { key: auth3.key, value: auth3.value };
|
|
@@ -7610,7 +7614,14 @@ function resolveAuth(auth3, resolve) {
|
|
|
7610
7614
|
case "apikey":
|
|
7611
7615
|
return { ...auth3, key: resolve(auth3.key), value: resolve(auth3.value) };
|
|
7612
7616
|
case "awsv4":
|
|
7613
|
-
return {
|
|
7617
|
+
return {
|
|
7618
|
+
...auth3,
|
|
7619
|
+
accessKey: resolve(auth3.accessKey),
|
|
7620
|
+
secretKey: resolve(auth3.secretKey),
|
|
7621
|
+
sessionToken: resolve(auth3.sessionToken),
|
|
7622
|
+
region: resolve(auth3.region),
|
|
7623
|
+
service: resolve(auth3.service)
|
|
7624
|
+
};
|
|
7614
7625
|
case "oauth2":
|
|
7615
7626
|
return {
|
|
7616
7627
|
...auth3,
|
|
@@ -7652,55 +7663,117 @@ function credentialsOf(auth3) {
|
|
|
7652
7663
|
}
|
|
7653
7664
|
}
|
|
7654
7665
|
|
|
7655
|
-
// ../core/src/
|
|
7666
|
+
// ../core/src/vault/vault-crypto.ts
|
|
7667
|
+
var KDF_ITERATIONS = 6e5;
|
|
7668
|
+
var MAX_KDF_ITERATIONS = 1e7;
|
|
7669
|
+
var IV_BYTES = 12;
|
|
7670
|
+
var AES = { name: "AES-GCM", length: 256 };
|
|
7671
|
+
var VAULT_FILE_FORMAT = "wayfarer/vault";
|
|
7672
|
+
function fromBase64Url(text3) {
|
|
7673
|
+
const binary = atob(text3.replace(/-/g, "+").replace(/_/g, "/"));
|
|
7674
|
+
return Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
7675
|
+
}
|
|
7656
7676
|
var utf8 = (text3) => new TextEncoder().encode(text3);
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7677
|
+
async function deriveKek(passphrase, kdf) {
|
|
7678
|
+
if (!crypto.subtle) {
|
|
7679
|
+
throw new Error("The vault needs a secure page: open Wayfarer over https or on localhost.");
|
|
7680
|
+
}
|
|
7681
|
+
const material = await crypto.subtle.importKey("raw", utf8(passphrase), "PBKDF2", false, ["deriveKey"]);
|
|
7682
|
+
return crypto.subtle.deriveKey(
|
|
7683
|
+
{ name: "PBKDF2", salt: fromBase64Url(kdf.salt), iterations: kdf.iterations, hash: "SHA-256" },
|
|
7684
|
+
material,
|
|
7685
|
+
AES,
|
|
7686
|
+
false,
|
|
7687
|
+
["wrapKey", "unwrapKey"]
|
|
7688
|
+
);
|
|
7689
|
+
}
|
|
7690
|
+
async function unwrap(record, passphrase, extractable) {
|
|
7691
|
+
const bytes = fromBase64Url(record.wrappedDek);
|
|
7692
|
+
const kek = await deriveKek(passphrase, record.kdf);
|
|
7662
7693
|
try {
|
|
7663
|
-
return
|
|
7694
|
+
return await crypto.subtle.unwrapKey(
|
|
7695
|
+
"raw",
|
|
7696
|
+
bytes.slice(IV_BYTES),
|
|
7697
|
+
kek,
|
|
7698
|
+
{ name: "AES-GCM", iv: bytes.slice(0, IV_BYTES) },
|
|
7699
|
+
AES,
|
|
7700
|
+
extractable,
|
|
7701
|
+
["encrypt", "decrypt"]
|
|
7702
|
+
);
|
|
7664
7703
|
} catch (error) {
|
|
7665
|
-
if (
|
|
7666
|
-
|
|
7704
|
+
if (error instanceof Error && error.name === "OperationError") return null;
|
|
7705
|
+
throw error;
|
|
7667
7706
|
}
|
|
7668
|
-
}
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
const
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
const
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7707
|
+
}
|
|
7708
|
+
function unwrapDek(record, passphrase) {
|
|
7709
|
+
return unwrap(record, passphrase, false);
|
|
7710
|
+
}
|
|
7711
|
+
async function decryptSecret(dek, id, envelope) {
|
|
7712
|
+
const plain = await crypto.subtle.decrypt(
|
|
7713
|
+
{ name: "AES-GCM", iv: fromBase64Url(envelope.iv), additionalData: utf8(id) },
|
|
7714
|
+
dek,
|
|
7715
|
+
fromBase64Url(envelope.ct)
|
|
7716
|
+
);
|
|
7717
|
+
return new TextDecoder().decode(plain);
|
|
7718
|
+
}
|
|
7719
|
+
function field(value, key) {
|
|
7720
|
+
return typeof value === "object" && value !== null && Object.hasOwn(value, key) ? value[key] : void 0;
|
|
7721
|
+
}
|
|
7722
|
+
var isBase64Url = (value) => typeof value === "string" && /^[\w-]+$/.test(value);
|
|
7723
|
+
function validateVaultFile(value) {
|
|
7724
|
+
const issues = [];
|
|
7725
|
+
const fail = (path, message) => issues.push({ path, message });
|
|
7726
|
+
if (typeof value !== "object" || value === null) {
|
|
7727
|
+
fail("$", "Value must be an object.");
|
|
7728
|
+
return issues;
|
|
7689
7729
|
}
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
const
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7730
|
+
if (field(value, "$id") !== VAULT_FILE_FORMAT) {
|
|
7731
|
+
fail("$id", `Not a Wayfarer vault file: "$id" must be "${VAULT_FILE_FORMAT}".`);
|
|
7732
|
+
return issues;
|
|
7733
|
+
}
|
|
7734
|
+
const vault = field(value, "vault");
|
|
7735
|
+
if (typeof vault !== "object" || vault === null) {
|
|
7736
|
+
fail("vault", "Value must be an object.");
|
|
7737
|
+
} else {
|
|
7738
|
+
const kdf = field(vault, "kdf");
|
|
7739
|
+
if (typeof kdf !== "object" || kdf === null) {
|
|
7740
|
+
fail("vault.kdf", "Value must be an object.");
|
|
7741
|
+
} else {
|
|
7742
|
+
if (field(kdf, "alg") !== "PBKDF2-SHA256") fail("vault.kdf.alg", 'Value must be "PBKDF2-SHA256".');
|
|
7743
|
+
const iterations = field(kdf, "iterations");
|
|
7744
|
+
if (!(Number.isInteger(iterations) && iterations >= KDF_ITERATIONS && iterations <= MAX_KDF_ITERATIONS)) {
|
|
7745
|
+
fail("vault.kdf.iterations", `Value must be a whole number from ${KDF_ITERATIONS} to ${MAX_KDF_ITERATIONS}.`);
|
|
7746
|
+
}
|
|
7747
|
+
if (!isBase64Url(field(kdf, "salt"))) fail("vault.kdf.salt", "Value must be base64url text.");
|
|
7748
|
+
}
|
|
7749
|
+
if (!isBase64Url(field(vault, "wrappedDek"))) fail("vault.wrappedDek", "Value must be base64url text.");
|
|
7750
|
+
}
|
|
7751
|
+
const secrets = field(value, "secrets");
|
|
7752
|
+
if (!Array.isArray(secrets)) {
|
|
7753
|
+
fail("secrets", "Value must be an array.");
|
|
7754
|
+
return issues;
|
|
7755
|
+
}
|
|
7756
|
+
secrets.forEach((secret, index) => {
|
|
7757
|
+
const path = `secrets[${index}]`;
|
|
7758
|
+
if (typeof secret !== "object" || secret === null) {
|
|
7759
|
+
fail(path, "Value must be an object.");
|
|
7760
|
+
return;
|
|
7761
|
+
}
|
|
7762
|
+
const id = field(secret, "id");
|
|
7763
|
+
if (typeof id !== "string" || !id) fail(`${path}.id`, "Value must be a non-empty string.");
|
|
7764
|
+
if (typeof field(secret, "name") !== "string") fail(`${path}.name`, "Value must be a string.");
|
|
7765
|
+
const environmentId = field(secret, "environmentId");
|
|
7766
|
+
if (environmentId !== void 0 && typeof environmentId !== "string")
|
|
7767
|
+
fail(`${path}.environmentId`, "Value must be a string.");
|
|
7768
|
+
const envelope = field(secret, "envelope");
|
|
7769
|
+
if (typeof envelope !== "object" || envelope === null) {
|
|
7770
|
+
fail(`${path}.envelope`, "Value must be an object.");
|
|
7771
|
+
return;
|
|
7772
|
+
}
|
|
7773
|
+
if (!isBase64Url(field(envelope, "iv"))) fail(`${path}.envelope.iv`, "Value must be base64url text.");
|
|
7774
|
+
if (!isBase64Url(field(envelope, "ct"))) fail(`${path}.envelope.ct`, "Value must be base64url text.");
|
|
7775
|
+
});
|
|
7776
|
+
return issues;
|
|
7704
7777
|
}
|
|
7705
7778
|
|
|
7706
7779
|
// ../core/src/auth/oauth2.ts
|
|
@@ -7708,7 +7781,13 @@ var OAuthError = class extends Error {
|
|
|
7708
7781
|
name = "OAuthError";
|
|
7709
7782
|
};
|
|
7710
7783
|
function oauthTokenKey(auth3) {
|
|
7711
|
-
return JSON.stringify([
|
|
7784
|
+
return JSON.stringify([
|
|
7785
|
+
auth3.grant,
|
|
7786
|
+
auth3.tokenUrl,
|
|
7787
|
+
auth3.clientId,
|
|
7788
|
+
auth3.scope,
|
|
7789
|
+
auth3.grant === "password" ? auth3.username : ""
|
|
7790
|
+
]);
|
|
7712
7791
|
}
|
|
7713
7792
|
|
|
7714
7793
|
// ../core/src/model/collection.ts
|
|
@@ -7725,15 +7804,50 @@ function folderChain(folders, folderId) {
|
|
|
7725
7804
|
}
|
|
7726
7805
|
return chain;
|
|
7727
7806
|
}
|
|
7728
|
-
function childrenInOrder(tree
|
|
7729
|
-
const
|
|
7730
|
-
|
|
7731
|
-
|
|
7807
|
+
function childrenInOrder(tree) {
|
|
7808
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
7809
|
+
const add = (parent, entry) => {
|
|
7810
|
+
const siblings = byParent.get(parent);
|
|
7811
|
+
if (siblings) siblings.push(entry);
|
|
7812
|
+
else byParent.set(parent, [entry]);
|
|
7813
|
+
};
|
|
7814
|
+
for (const folder of tree.folders)
|
|
7815
|
+
add(folder.parentFolderId, { order: folder.order, kind: 0, id: folder.id, child: { folder } });
|
|
7816
|
+
for (const request of tree.requests)
|
|
7817
|
+
add(request.folderId, { order: request.order, kind: 1, id: request.id, child: { request } });
|
|
7818
|
+
const sorted = new Map(
|
|
7819
|
+
[...byParent].map(([parent, entries]) => [
|
|
7820
|
+
parent,
|
|
7821
|
+
entries.sort((a, b) => a.order - b.order || a.kind - b.kind || a.id.localeCompare(b.id)).map((entry) => entry.child)
|
|
7822
|
+
])
|
|
7823
|
+
);
|
|
7824
|
+
return (parent) => sorted.get(parent) ?? [];
|
|
7825
|
+
}
|
|
7826
|
+
function treeInOrder(tree) {
|
|
7827
|
+
const entered = /* @__PURE__ */ new Set();
|
|
7828
|
+
const childrenOf = childrenInOrder(tree);
|
|
7829
|
+
const walk2 = (parent) => childrenOf(parent).flatMap((child) => {
|
|
7830
|
+
if (child.request !== void 0) return [{ kind: "request", request: child.request }];
|
|
7831
|
+
const { folder } = child;
|
|
7832
|
+
if (!folder || entered.has(folder.id)) return [];
|
|
7833
|
+
entered.add(folder.id);
|
|
7834
|
+
return [{ kind: "folder", folder }, ...walk2(folder.id)];
|
|
7835
|
+
});
|
|
7836
|
+
const listed = walk2(void 0);
|
|
7837
|
+
const seen = new Set(listed.map((item) => item.kind === "request" ? item.request : item.folder));
|
|
7838
|
+
return [
|
|
7839
|
+
...listed,
|
|
7840
|
+
...tree.folders.filter((folder) => !seen.has(folder)).map((folder) => ({ kind: "folder", folder })),
|
|
7841
|
+
...tree.requests.filter((request) => !seen.has(request)).map((request) => ({ kind: "request", request }))
|
|
7732
7842
|
];
|
|
7733
|
-
return entries.sort((a, b) => a.order - b.order || a.kind - b.kind || a.id.localeCompare(b.id)).map((entry) => entry.child);
|
|
7734
7843
|
}
|
|
7735
7844
|
function ancestorsOf(collection, folders, folderId) {
|
|
7736
|
-
const named2 = (kind, { name, variables: variables2, auth: auth3, scripts: scripts2 }) => ({
|
|
7845
|
+
const named2 = (kind, { name, variables: variables2, auth: auth3, scripts: scripts2 }) => ({
|
|
7846
|
+
name: `${kind} "${name}"`,
|
|
7847
|
+
variables: variables2,
|
|
7848
|
+
auth: auth3,
|
|
7849
|
+
scripts: scripts2
|
|
7850
|
+
});
|
|
7737
7851
|
return [named2("collection", collection), ...folderChain(folders, folderId).map((folder) => named2("folder", folder))];
|
|
7738
7852
|
}
|
|
7739
7853
|
var ENVIRONMENTS_FORMAT = "wayfarer/environments";
|
|
@@ -7752,7 +7866,15 @@ var RAW_CONTENT_TYPES = {
|
|
|
7752
7866
|
javascript: "application/javascript"
|
|
7753
7867
|
};
|
|
7754
7868
|
var OAUTH_GRANTS = ["authorization_code", "client_credentials", "password"];
|
|
7755
|
-
var AUTH_TYPES = [
|
|
7869
|
+
var AUTH_TYPES = [
|
|
7870
|
+
"inherit",
|
|
7871
|
+
"none",
|
|
7872
|
+
"bearer",
|
|
7873
|
+
"basic",
|
|
7874
|
+
"apikey",
|
|
7875
|
+
"awsv4",
|
|
7876
|
+
"oauth2"
|
|
7877
|
+
];
|
|
7756
7878
|
var ASSERTION_TARGETS = ["status", "body", "header", "duration"];
|
|
7757
7879
|
var ASSERTION_OPERATORS = [
|
|
7758
7880
|
"equals",
|
|
@@ -7784,7 +7906,7 @@ function emptyRequest() {
|
|
|
7784
7906
|
function isObject(value) {
|
|
7785
7907
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7786
7908
|
}
|
|
7787
|
-
function
|
|
7909
|
+
function field2(object, key) {
|
|
7788
7910
|
return Object.hasOwn(object, key) ? object[key] : void 0;
|
|
7789
7911
|
}
|
|
7790
7912
|
var Checker = class {
|
|
@@ -7798,12 +7920,12 @@ var Checker = class {
|
|
|
7798
7920
|
return null;
|
|
7799
7921
|
}
|
|
7800
7922
|
string(object, key, path, optional = false) {
|
|
7801
|
-
const value =
|
|
7923
|
+
const value = field2(object, key);
|
|
7802
7924
|
if (typeof value === "string" || optional && value === void 0) return;
|
|
7803
7925
|
this.fail(`${path}.${key}`, "Value must be a string.");
|
|
7804
7926
|
}
|
|
7805
7927
|
oneOf(object, key, path, allowed, optional = false) {
|
|
7806
|
-
const value =
|
|
7928
|
+
const value = field2(object, key);
|
|
7807
7929
|
if (optional && value === void 0 || typeof value === "string" && allowed.includes(value)) return;
|
|
7808
7930
|
this.fail(`${path}.${key}`, `Value must be one of ${allowed.join(", ")}.`);
|
|
7809
7931
|
}
|
|
@@ -7819,7 +7941,7 @@ var Checker = class {
|
|
|
7819
7941
|
});
|
|
7820
7942
|
}
|
|
7821
7943
|
enabled(row, rowPath) {
|
|
7822
|
-
if (typeof
|
|
7944
|
+
if (typeof field2(row, "enabled") !== "boolean") {
|
|
7823
7945
|
this.fail(`${rowPath}.enabled`, "Value must be true or false.");
|
|
7824
7946
|
}
|
|
7825
7947
|
}
|
|
@@ -7828,7 +7950,8 @@ var Checker = class {
|
|
|
7828
7950
|
this.string(row, "key", rowPath);
|
|
7829
7951
|
this.string(row, "value", rowPath);
|
|
7830
7952
|
this.enabled(row, rowPath);
|
|
7831
|
-
if (Object.hasOwn(row, "json") && (
|
|
7953
|
+
if (Object.hasOwn(row, "json") && (field2(row, "json") !== true || !isJsonText(field2(row, "value"))))
|
|
7954
|
+
this.fail(`${rowPath}.json`, "Value must be true, on a value that is JSON.");
|
|
7832
7955
|
});
|
|
7833
7956
|
}
|
|
7834
7957
|
/** `inherit` is for a request and a folder: a collection has nothing above it. */
|
|
@@ -7836,7 +7959,7 @@ var Checker = class {
|
|
|
7836
7959
|
const auth3 = this.object(value, path);
|
|
7837
7960
|
if (!auth3) return;
|
|
7838
7961
|
this.oneOf(auth3, "type", path, inherit ? AUTH_TYPES : AUTH_TYPES.filter((type2) => type2 !== "inherit"));
|
|
7839
|
-
const type =
|
|
7962
|
+
const type = field2(auth3, "type");
|
|
7840
7963
|
if (type === "bearer") {
|
|
7841
7964
|
this.string(auth3, "token", path);
|
|
7842
7965
|
} else if (type === "basic") {
|
|
@@ -7850,9 +7973,11 @@ var Checker = class {
|
|
|
7850
7973
|
for (const key of ["accessKey", "secretKey", "sessionToken", "region", "service"]) this.string(auth3, key, path);
|
|
7851
7974
|
} else if (type === "oauth2") {
|
|
7852
7975
|
this.oneOf(auth3, "grant", path, OAUTH_GRANTS);
|
|
7853
|
-
for (const key of ["authUrl", "tokenUrl", "clientId", "clientSecret", "scope", "username", "password"])
|
|
7976
|
+
for (const key of ["authUrl", "tokenUrl", "clientId", "clientSecret", "scope", "username", "password"])
|
|
7977
|
+
this.string(auth3, key, path);
|
|
7854
7978
|
this.oneOf(auth3, "clientAuth", path, ["header", "body"]);
|
|
7855
|
-
if (typeof
|
|
7979
|
+
if (typeof field2(auth3, "retryOn401") !== "boolean")
|
|
7980
|
+
this.fail(`${path}.retryOn401`, "Value must be true or false.");
|
|
7856
7981
|
}
|
|
7857
7982
|
}
|
|
7858
7983
|
scripts(value, path) {
|
|
@@ -7875,9 +8000,9 @@ function validateInherited(value, path, kind) {
|
|
|
7875
8000
|
const check = new Checker();
|
|
7876
8001
|
const node = check.object(value, path);
|
|
7877
8002
|
if (!node) return check.issues;
|
|
7878
|
-
check.auth(
|
|
7879
|
-
check.scripts(
|
|
7880
|
-
if (kind === "folder") check.rows(
|
|
8003
|
+
check.auth(field2(node, "auth"), `${path}.auth`, kind === "folder");
|
|
8004
|
+
check.scripts(field2(node, "scripts"), `${path}.scripts`);
|
|
8005
|
+
if (kind === "folder") check.rows(field2(node, "variables"), `${path}.variables`);
|
|
7881
8006
|
else check.oneOf(node, "route", path, ["auto", "direct", "bridge"], true);
|
|
7882
8007
|
return check.issues;
|
|
7883
8008
|
}
|
|
@@ -7885,75 +8010,75 @@ function validateRequestContent(value, path) {
|
|
|
7885
8010
|
const check = new Checker();
|
|
7886
8011
|
const request = check.object(value, path);
|
|
7887
8012
|
if (!request) return check.issues;
|
|
7888
|
-
const method =
|
|
8013
|
+
const method = field2(request, "method");
|
|
7889
8014
|
if (typeof method !== "string" || !isHttpMethod(method)) {
|
|
7890
8015
|
check.fail(`${path}.method`, "Value must be an HTTP method: one word of at most 32 characters, in upper case.");
|
|
7891
8016
|
}
|
|
7892
|
-
if (typeof
|
|
8017
|
+
if (typeof field2(request, "url") !== "string" || !field2(request, "url").trim()) {
|
|
7893
8018
|
check.fail(`${path}.url`, "Value must be a non-empty string.");
|
|
7894
8019
|
}
|
|
7895
|
-
check.rows(
|
|
7896
|
-
check.rows(
|
|
7897
|
-
const body3 = check.object(
|
|
8020
|
+
check.rows(field2(request, "params"), `${path}.params`);
|
|
8021
|
+
check.rows(field2(request, "headers"), `${path}.headers`);
|
|
8022
|
+
const body3 = check.object(field2(request, "body"), `${path}.body`);
|
|
7898
8023
|
if (body3) {
|
|
7899
|
-
const mode =
|
|
8024
|
+
const mode = field2(body3, "mode");
|
|
7900
8025
|
check.oneOf(body3, "mode", `${path}.body`, BODY_MODES);
|
|
7901
|
-
const part = (name) =>
|
|
8026
|
+
const part = (name) => field2(body3, name) !== void 0 || mode === name ? name : null;
|
|
7902
8027
|
if (part("raw")) {
|
|
7903
|
-
const raw = check.object(
|
|
8028
|
+
const raw = check.object(field2(body3, "raw"), `${path}.body.raw`);
|
|
7904
8029
|
if (raw) {
|
|
7905
8030
|
check.oneOf(raw, "language", `${path}.body.raw`, RAW_LANGUAGES);
|
|
7906
8031
|
check.string(raw, "text", `${path}.body.raw`);
|
|
7907
8032
|
}
|
|
7908
8033
|
}
|
|
7909
8034
|
if (part("urlencoded")) {
|
|
7910
|
-
check.rows(
|
|
8035
|
+
check.rows(field2(body3, "urlencoded"), `${path}.body.urlencoded`);
|
|
7911
8036
|
}
|
|
7912
8037
|
if (part("multipart")) {
|
|
7913
|
-
check.list(
|
|
8038
|
+
check.list(field2(body3, "multipart"), `${path}.body.multipart`, (item, itemPath) => {
|
|
7914
8039
|
check.string(item, "key", itemPath);
|
|
7915
8040
|
check.enabled(item, itemPath);
|
|
7916
8041
|
check.oneOf(item, "kind", itemPath, ["text", "file"]);
|
|
7917
|
-
if (
|
|
7918
|
-
if (
|
|
8042
|
+
if (field2(item, "kind") === "text") check.string(item, "value", itemPath);
|
|
8043
|
+
if (field2(item, "kind") === "file") check.fileRef(item, itemPath);
|
|
7919
8044
|
});
|
|
7920
8045
|
}
|
|
7921
8046
|
if (part("graphql")) {
|
|
7922
|
-
const graphql = check.object(
|
|
8047
|
+
const graphql = check.object(field2(body3, "graphql"), `${path}.body.graphql`);
|
|
7923
8048
|
if (graphql) {
|
|
7924
8049
|
check.string(graphql, "query", `${path}.body.graphql`);
|
|
7925
8050
|
check.string(graphql, "variables", `${path}.body.graphql`);
|
|
7926
8051
|
}
|
|
7927
8052
|
}
|
|
7928
|
-
if (
|
|
7929
|
-
const binary = check.object(
|
|
8053
|
+
if (field2(body3, "binary") !== void 0) {
|
|
8054
|
+
const binary = check.object(field2(body3, "binary"), `${path}.body.binary`);
|
|
7930
8055
|
if (binary) {
|
|
7931
8056
|
check.fileRef(binary, `${path}.body.binary`);
|
|
7932
8057
|
check.string(binary, "contentType", `${path}.body.binary`, true);
|
|
7933
8058
|
}
|
|
7934
8059
|
}
|
|
7935
8060
|
}
|
|
7936
|
-
check.auth(
|
|
7937
|
-
check.scripts(
|
|
7938
|
-
check.list(
|
|
8061
|
+
check.auth(field2(request, "auth"), `${path}.auth`, true);
|
|
8062
|
+
check.scripts(field2(request, "scripts"), `${path}.scripts`);
|
|
8063
|
+
check.list(field2(request, "tests"), `${path}.tests`, (test, testPath) => {
|
|
7939
8064
|
check.string(test, "id", testPath);
|
|
7940
8065
|
check.oneOf(test, "target", testPath, ASSERTION_TARGETS);
|
|
7941
8066
|
check.oneOf(test, "operator", testPath, ASSERTION_OPERATORS);
|
|
7942
8067
|
check.string(test, "key", testPath, true);
|
|
7943
8068
|
check.string(test, "expected", testPath, true);
|
|
7944
8069
|
});
|
|
7945
|
-
const settings = check.object(
|
|
8070
|
+
const settings = check.object(field2(request, "settings"), `${path}.settings`);
|
|
7946
8071
|
if (settings) {
|
|
7947
|
-
const timeout =
|
|
8072
|
+
const timeout = field2(settings, "timeoutMs");
|
|
7948
8073
|
if (timeout !== void 0 && !(typeof timeout === "number" && Number.isFinite(timeout) && timeout >= 0)) {
|
|
7949
8074
|
check.fail(`${path}.settings.timeoutMs`, "Value must be a number, 0 or more.");
|
|
7950
8075
|
}
|
|
7951
|
-
const follow =
|
|
8076
|
+
const follow = field2(settings, "followRedirects");
|
|
7952
8077
|
if (follow !== void 0 && typeof follow !== "boolean") {
|
|
7953
8078
|
check.fail(`${path}.settings.followRedirects`, "Value must be true or false.");
|
|
7954
8079
|
}
|
|
7955
8080
|
check.oneOf(settings, "route", `${path}.settings`, ["auto", "direct", "bridge"], true);
|
|
7956
|
-
const insecure =
|
|
8081
|
+
const insecure = field2(settings, "tlsInsecure");
|
|
7957
8082
|
if (insecure !== void 0 && typeof insecure !== "boolean") {
|
|
7958
8083
|
check.fail(`${path}.settings.tlsInsecure`, "Value must be true or false.");
|
|
7959
8084
|
}
|
|
@@ -7989,16 +8114,12 @@ function validateCollection(input) {
|
|
|
7989
8114
|
if (!Array.isArray(value.folders)) {
|
|
7990
8115
|
errors.push({ path: "folders", message: "Folders must be an array." });
|
|
7991
8116
|
} else {
|
|
7992
|
-
value.folders.forEach(
|
|
7993
|
-
(folder, index) => validateFolderDoc(folder, `folders[${index}]`, errors)
|
|
7994
|
-
);
|
|
8117
|
+
value.folders.forEach((folder, index) => validateFolderDoc(folder, `folders[${index}]`, errors));
|
|
7995
8118
|
}
|
|
7996
8119
|
if (!Array.isArray(value.requests)) {
|
|
7997
8120
|
errors.push({ path: "requests", message: "Requests must be an array." });
|
|
7998
8121
|
} else {
|
|
7999
|
-
value.requests.forEach(
|
|
8000
|
-
(request, index) => validateRequestDoc(request, `requests[${index}]`, errors)
|
|
8001
|
-
);
|
|
8122
|
+
value.requests.forEach((request, index) => validateRequestDoc(request, `requests[${index}]`, errors));
|
|
8002
8123
|
}
|
|
8003
8124
|
if (!value.meta) {
|
|
8004
8125
|
errors.push({ path: "meta", message: "Missing export meta block." });
|
|
@@ -8047,25 +8168,14 @@ function buildPlan(payload, duplicate) {
|
|
|
8047
8168
|
{
|
|
8048
8169
|
type: "collection",
|
|
8049
8170
|
name: payload.collection.name,
|
|
8050
|
-
id: payload.collection.
|
|
8171
|
+
id: payload.collection.meta.id,
|
|
8051
8172
|
action
|
|
8052
8173
|
}
|
|
8053
8174
|
];
|
|
8054
|
-
for (const
|
|
8055
|
-
entries.push(
|
|
8056
|
-
type: "folder",
|
|
8057
|
-
|
|
8058
|
-
id: folder.id ?? folder.meta.id,
|
|
8059
|
-
action
|
|
8060
|
-
});
|
|
8061
|
-
}
|
|
8062
|
-
for (const request of payload.requests) {
|
|
8063
|
-
entries.push({
|
|
8064
|
-
type: "request",
|
|
8065
|
-
name: request.name || request.url,
|
|
8066
|
-
id: request.id ?? request.meta.id,
|
|
8067
|
-
action
|
|
8068
|
-
});
|
|
8175
|
+
for (const child of treeInOrder(payload)) {
|
|
8176
|
+
entries.push(
|
|
8177
|
+
child.kind === "request" ? { type: "request", name: child.request.name || child.request.url, id: child.request.meta.id, action } : { type: "folder", name: child.folder.name, id: child.folder.meta.id, action }
|
|
8178
|
+
);
|
|
8069
8179
|
}
|
|
8070
8180
|
return entries;
|
|
8071
8181
|
}
|
|
@@ -8176,9 +8286,7 @@ function touchMeta(meta) {
|
|
|
8176
8286
|
return { ...meta, updatedAt: Date.now() };
|
|
8177
8287
|
}
|
|
8178
8288
|
function ensureDocId(doc) {
|
|
8179
|
-
|
|
8180
|
-
doc.id = doc.meta.id;
|
|
8181
|
-
}
|
|
8289
|
+
doc.id = doc.meta.id;
|
|
8182
8290
|
return doc;
|
|
8183
8291
|
}
|
|
8184
8292
|
|
|
@@ -8242,7 +8350,8 @@ function graphqlPayload(query, variables2) {
|
|
|
8242
8350
|
if (!variables2.trim()) return { text: JSON.stringify({ query }) };
|
|
8243
8351
|
const parsed2 = parseJson(variables2);
|
|
8244
8352
|
if (!parsed2.ok) return { error: "The GraphQL variables are not JSON." };
|
|
8245
|
-
if (typeof parsed2.value !== "object" || parsed2.value === null || Array.isArray(parsed2.value))
|
|
8353
|
+
if (typeof parsed2.value !== "object" || parsed2.value === null || Array.isArray(parsed2.value))
|
|
8354
|
+
return { error: "The GraphQL variables must be a JSON object." };
|
|
8246
8355
|
return { text: JSON.stringify({ query, variables: parsed2.value }) };
|
|
8247
8356
|
}
|
|
8248
8357
|
function graphqlText(query, variables2) {
|
|
@@ -8317,7 +8426,10 @@ var Redactor = class {
|
|
|
8317
8426
|
}
|
|
8318
8427
|
/** Headers as sent or received: a credential header's value is masked whole. */
|
|
8319
8428
|
headers(headers2, options = {}) {
|
|
8320
|
-
return headers2.map(([name, value]) => [
|
|
8429
|
+
return headers2.map(([name, value]) => [
|
|
8430
|
+
this.text(name),
|
|
8431
|
+
!options.credentials && isCredentialHeader(name) ? MASK : this.text(value)
|
|
8432
|
+
]);
|
|
8321
8433
|
}
|
|
8322
8434
|
/**
|
|
8323
8435
|
* Rows of a template (headers, or variables by name): a value typed in for
|
|
@@ -8325,7 +8437,10 @@ var Redactor = class {
|
|
|
8325
8437
|
* credential but a reference to one, and is kept.
|
|
8326
8438
|
*/
|
|
8327
8439
|
rows(rows2, options = {}) {
|
|
8328
|
-
return rows2.map((row) => ({
|
|
8440
|
+
return rows2.map((row) => ({
|
|
8441
|
+
...row,
|
|
8442
|
+
value: isCredentialHeader(row.key) ? this.credential(row.value, options) : this.text(row.value)
|
|
8443
|
+
}));
|
|
8329
8444
|
}
|
|
8330
8445
|
auth(auth3, options = {}) {
|
|
8331
8446
|
switch (auth3.type) {
|
|
@@ -8336,9 +8451,17 @@ var Redactor = class {
|
|
|
8336
8451
|
case "apikey":
|
|
8337
8452
|
return { ...auth3, value: this.credential(auth3.value, options) };
|
|
8338
8453
|
case "awsv4":
|
|
8339
|
-
return {
|
|
8454
|
+
return {
|
|
8455
|
+
...auth3,
|
|
8456
|
+
secretKey: this.credential(auth3.secretKey, options),
|
|
8457
|
+
sessionToken: this.credential(auth3.sessionToken, options)
|
|
8458
|
+
};
|
|
8340
8459
|
case "oauth2":
|
|
8341
|
-
return {
|
|
8460
|
+
return {
|
|
8461
|
+
...auth3,
|
|
8462
|
+
clientSecret: this.credential(auth3.clientSecret, options),
|
|
8463
|
+
password: this.credential(auth3.password, options)
|
|
8464
|
+
};
|
|
8342
8465
|
case "none":
|
|
8343
8466
|
case "inherit":
|
|
8344
8467
|
return auth3;
|
|
@@ -8359,9 +8482,15 @@ var Redactor = class {
|
|
|
8359
8482
|
return {
|
|
8360
8483
|
...body3,
|
|
8361
8484
|
...body3.raw && { raw: { ...body3.raw, text: this.text(body3.raw.text) } },
|
|
8362
|
-
...body3.graphql && {
|
|
8485
|
+
...body3.graphql && {
|
|
8486
|
+
graphql: { query: this.text(body3.graphql.query), variables: this.text(body3.graphql.variables) }
|
|
8487
|
+
},
|
|
8363
8488
|
...body3.urlencoded && { urlencoded: body3.urlencoded.map((row) => ({ ...row, value: this.text(row.value) })) },
|
|
8364
|
-
...body3.multipart && {
|
|
8489
|
+
...body3.multipart && {
|
|
8490
|
+
multipart: body3.multipart.map(
|
|
8491
|
+
(part) => part.kind === "text" ? { ...part, value: this.text(part.value) } : part
|
|
8492
|
+
)
|
|
8493
|
+
}
|
|
8365
8494
|
};
|
|
8366
8495
|
}
|
|
8367
8496
|
credential(value, options) {
|
|
@@ -8375,6 +8504,53 @@ function isCurlCommand(text3) {
|
|
|
8375
8504
|
return /^\s*curl(\.exe)?(\s|$)/i.test(text3);
|
|
8376
8505
|
}
|
|
8377
8506
|
|
|
8507
|
+
// ../core/src/import/script-scan.ts
|
|
8508
|
+
var START = String.raw`(?<![\w$.])`;
|
|
8509
|
+
var escape = (text3) => text3.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
8510
|
+
var member = (name) => ({
|
|
8511
|
+
api: name,
|
|
8512
|
+
finds: [[name, new RegExp(`${START}${escape(name)}(?![\\w$])`)]]
|
|
8513
|
+
});
|
|
8514
|
+
var required = (module) => [
|
|
8515
|
+
`require('${module}')`,
|
|
8516
|
+
new RegExp(`${START}require\\(\\s*["']${escape(module)}["']\\s*\\)`)
|
|
8517
|
+
];
|
|
8518
|
+
var called = (name) => [name, new RegExp(`${START}${escape(name)}\\s*\\(`)];
|
|
8519
|
+
var used = (name) => [name, new RegExp(`${START}${escape(name)}\\s*[.(]`)];
|
|
8520
|
+
var SCRIPT_SCAN = [
|
|
8521
|
+
...[
|
|
8522
|
+
"pm.cookies.get",
|
|
8523
|
+
"pm.cookies.has",
|
|
8524
|
+
"pm.cookies.toObject",
|
|
8525
|
+
"pm.cookies.jar",
|
|
8526
|
+
"pm.visualizer.set",
|
|
8527
|
+
"pm.vault.get",
|
|
8528
|
+
"pm.require",
|
|
8529
|
+
"postman.getResponseCookie"
|
|
8530
|
+
].map(member),
|
|
8531
|
+
{
|
|
8532
|
+
api: "legacy library globals (_, CryptoJS, tv4, cheerio, xml2Json, Backbone)",
|
|
8533
|
+
finds: ["tv4", "cheerio", "xml2Json", "Backbone"].map(used)
|
|
8534
|
+
},
|
|
8535
|
+
{
|
|
8536
|
+
api: "clearTimeout, setInterval, clearInterval",
|
|
8537
|
+
finds: ["clearTimeout", "setInterval", "clearInterval"].map(called)
|
|
8538
|
+
},
|
|
8539
|
+
...["ajv", "cheerio", "csv-parse/lib/sync", "postman-collection", "tv4", "xml2js"].map((module) => ({
|
|
8540
|
+
api: `require('${module}')`,
|
|
8541
|
+
finds: [required(module)]
|
|
8542
|
+
})),
|
|
8543
|
+
{
|
|
8544
|
+
api: "require of a Node module (buffer, events, path, querystring, stream, string_decoder, timers, url, util)",
|
|
8545
|
+
finds: ["buffer", "events", "path", "querystring", "stream", "string_decoder", "timers", "url", "util"].map(
|
|
8546
|
+
required
|
|
8547
|
+
)
|
|
8548
|
+
}
|
|
8549
|
+
];
|
|
8550
|
+
function unsupportedApis(script) {
|
|
8551
|
+
return SCRIPT_SCAN.flatMap((entry) => entry.finds).filter(([, pattern]) => pattern.test(script)).map(([label]) => label);
|
|
8552
|
+
}
|
|
8553
|
+
|
|
8378
8554
|
// ../core/src/model/variables.ts
|
|
8379
8555
|
var SECRET_REFERENCE = /^\s*\{\{\s*\$secret\.[a-z0-9-]+\s*\}\}\s*$/i;
|
|
8380
8556
|
function protectedSecretId(value) {
|
|
@@ -8382,10 +8558,11 @@ function protectedSecretId(value) {
|
|
|
8382
8558
|
}
|
|
8383
8559
|
function applyVariableChanges(rows2, changes) {
|
|
8384
8560
|
let next = rows2;
|
|
8385
|
-
for (const { key, value, keepSecret, json } of changes) {
|
|
8386
|
-
const made = () => ({ key, value: value ?? "", enabled: true, ...json && { json } });
|
|
8561
|
+
for (const { key, value, keepSecret, json, enabled } of changes) {
|
|
8562
|
+
const made = () => ({ key, value: value ?? "", enabled: enabled ?? true, ...json && { json } });
|
|
8387
8563
|
const first = next.findIndex((row) => row.key === key);
|
|
8388
|
-
if (value !== null && keepSecret && next.some((row) => row.key === key && SECRET_REFERENCE.test(row.value)))
|
|
8564
|
+
if (value !== null && keepSecret && next.some((row) => row.key === key && SECRET_REFERENCE.test(row.value)))
|
|
8565
|
+
continue;
|
|
8389
8566
|
if (value === null) {
|
|
8390
8567
|
next = next.filter((row) => row.key !== key);
|
|
8391
8568
|
} else if (first === -1) {
|
|
@@ -8401,122 +8578,11 @@ function variablesByName(rows2) {
|
|
|
8401
8578
|
}
|
|
8402
8579
|
function scriptVariables(rows2) {
|
|
8403
8580
|
const byName = /* @__PURE__ */ new Map();
|
|
8404
|
-
for (const row of rows2)
|
|
8581
|
+
for (const row of rows2)
|
|
8582
|
+
if (row.enabled && row.key) byName.set(row.key, row.json ? [row.key, row.value, true] : [row.key, row.value]);
|
|
8405
8583
|
return [...byName.values()];
|
|
8406
8584
|
}
|
|
8407
8585
|
|
|
8408
|
-
// ../core/src/vault/vault-crypto.ts
|
|
8409
|
-
var KDF_ITERATIONS = 6e5;
|
|
8410
|
-
var MAX_KDF_ITERATIONS = 1e7;
|
|
8411
|
-
var IV_BYTES = 12;
|
|
8412
|
-
var AES = { name: "AES-GCM", length: 256 };
|
|
8413
|
-
var VAULT_FILE_FORMAT = "wayfarer/vault";
|
|
8414
|
-
function fromBase64Url(text3) {
|
|
8415
|
-
const binary = atob(text3.replace(/-/g, "+").replace(/_/g, "/"));
|
|
8416
|
-
return Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
8417
|
-
}
|
|
8418
|
-
var utf82 = (text3) => new TextEncoder().encode(text3);
|
|
8419
|
-
async function deriveKek(passphrase, kdf) {
|
|
8420
|
-
if (!crypto.subtle) {
|
|
8421
|
-
throw new Error("The vault needs a secure page: open Wayfarer over https or on localhost.");
|
|
8422
|
-
}
|
|
8423
|
-
const material = await crypto.subtle.importKey("raw", utf82(passphrase), "PBKDF2", false, ["deriveKey"]);
|
|
8424
|
-
return crypto.subtle.deriveKey(
|
|
8425
|
-
{ name: "PBKDF2", salt: fromBase64Url(kdf.salt), iterations: kdf.iterations, hash: "SHA-256" },
|
|
8426
|
-
material,
|
|
8427
|
-
AES,
|
|
8428
|
-
false,
|
|
8429
|
-
["wrapKey", "unwrapKey"]
|
|
8430
|
-
);
|
|
8431
|
-
}
|
|
8432
|
-
async function unwrap(record, passphrase, extractable) {
|
|
8433
|
-
const bytes = fromBase64Url(record.wrappedDek);
|
|
8434
|
-
const kek = await deriveKek(passphrase, record.kdf);
|
|
8435
|
-
try {
|
|
8436
|
-
return await crypto.subtle.unwrapKey(
|
|
8437
|
-
"raw",
|
|
8438
|
-
bytes.slice(IV_BYTES),
|
|
8439
|
-
kek,
|
|
8440
|
-
{ name: "AES-GCM", iv: bytes.slice(0, IV_BYTES) },
|
|
8441
|
-
AES,
|
|
8442
|
-
extractable,
|
|
8443
|
-
["encrypt", "decrypt"]
|
|
8444
|
-
);
|
|
8445
|
-
} catch (error) {
|
|
8446
|
-
if (error instanceof Error && error.name === "OperationError") return null;
|
|
8447
|
-
throw error;
|
|
8448
|
-
}
|
|
8449
|
-
}
|
|
8450
|
-
function unwrapDek(record, passphrase) {
|
|
8451
|
-
return unwrap(record, passphrase, false);
|
|
8452
|
-
}
|
|
8453
|
-
async function decryptSecret(dek, id, envelope) {
|
|
8454
|
-
const plain = await crypto.subtle.decrypt(
|
|
8455
|
-
{ name: "AES-GCM", iv: fromBase64Url(envelope.iv), additionalData: utf82(id) },
|
|
8456
|
-
dek,
|
|
8457
|
-
fromBase64Url(envelope.ct)
|
|
8458
|
-
);
|
|
8459
|
-
return new TextDecoder().decode(plain);
|
|
8460
|
-
}
|
|
8461
|
-
function field2(value, key) {
|
|
8462
|
-
return typeof value === "object" && value !== null && Object.hasOwn(value, key) ? value[key] : void 0;
|
|
8463
|
-
}
|
|
8464
|
-
var isBase64Url = (value) => typeof value === "string" && /^[\w-]+$/.test(value);
|
|
8465
|
-
function validateVaultFile(value) {
|
|
8466
|
-
const issues = [];
|
|
8467
|
-
const fail = (path, message) => issues.push({ path, message });
|
|
8468
|
-
if (typeof value !== "object" || value === null) {
|
|
8469
|
-
fail("$", "Value must be an object.");
|
|
8470
|
-
return issues;
|
|
8471
|
-
}
|
|
8472
|
-
if (field2(value, "$id") !== VAULT_FILE_FORMAT) {
|
|
8473
|
-
fail("$id", `Not a Wayfarer vault file: "$id" must be "${VAULT_FILE_FORMAT}".`);
|
|
8474
|
-
return issues;
|
|
8475
|
-
}
|
|
8476
|
-
const vault = field2(value, "vault");
|
|
8477
|
-
if (typeof vault !== "object" || vault === null) {
|
|
8478
|
-
fail("vault", "Value must be an object.");
|
|
8479
|
-
} else {
|
|
8480
|
-
const kdf = field2(vault, "kdf");
|
|
8481
|
-
if (typeof kdf !== "object" || kdf === null) {
|
|
8482
|
-
fail("vault.kdf", "Value must be an object.");
|
|
8483
|
-
} else {
|
|
8484
|
-
if (field2(kdf, "alg") !== "PBKDF2-SHA256") fail("vault.kdf.alg", 'Value must be "PBKDF2-SHA256".');
|
|
8485
|
-
const iterations = field2(kdf, "iterations");
|
|
8486
|
-
if (!(Number.isInteger(iterations) && iterations >= KDF_ITERATIONS && iterations <= MAX_KDF_ITERATIONS)) {
|
|
8487
|
-
fail("vault.kdf.iterations", `Value must be a whole number from ${KDF_ITERATIONS} to ${MAX_KDF_ITERATIONS}.`);
|
|
8488
|
-
}
|
|
8489
|
-
if (!isBase64Url(field2(kdf, "salt"))) fail("vault.kdf.salt", "Value must be base64url text.");
|
|
8490
|
-
}
|
|
8491
|
-
if (!isBase64Url(field2(vault, "wrappedDek"))) fail("vault.wrappedDek", "Value must be base64url text.");
|
|
8492
|
-
}
|
|
8493
|
-
const secrets = field2(value, "secrets");
|
|
8494
|
-
if (!Array.isArray(secrets)) {
|
|
8495
|
-
fail("secrets", "Value must be an array.");
|
|
8496
|
-
return issues;
|
|
8497
|
-
}
|
|
8498
|
-
secrets.forEach((secret, index) => {
|
|
8499
|
-
const path = `secrets[${index}]`;
|
|
8500
|
-
if (typeof secret !== "object" || secret === null) {
|
|
8501
|
-
fail(path, "Value must be an object.");
|
|
8502
|
-
return;
|
|
8503
|
-
}
|
|
8504
|
-
const id = field2(secret, "id");
|
|
8505
|
-
if (typeof id !== "string" || !id) fail(`${path}.id`, "Value must be a non-empty string.");
|
|
8506
|
-
if (typeof field2(secret, "name") !== "string") fail(`${path}.name`, "Value must be a string.");
|
|
8507
|
-
const environmentId = field2(secret, "environmentId");
|
|
8508
|
-
if (environmentId !== void 0 && typeof environmentId !== "string") fail(`${path}.environmentId`, "Value must be a string.");
|
|
8509
|
-
const envelope = field2(secret, "envelope");
|
|
8510
|
-
if (typeof envelope !== "object" || envelope === null) {
|
|
8511
|
-
fail(`${path}.envelope`, "Value must be an object.");
|
|
8512
|
-
return;
|
|
8513
|
-
}
|
|
8514
|
-
if (!isBase64Url(field2(envelope, "iv"))) fail(`${path}.envelope.iv`, "Value must be base64url text.");
|
|
8515
|
-
if (!isBase64Url(field2(envelope, "ct"))) fail(`${path}.envelope.ct`, "Value must be base64url text.");
|
|
8516
|
-
});
|
|
8517
|
-
return issues;
|
|
8518
|
-
}
|
|
8519
|
-
|
|
8520
8586
|
// ../core/src/variables/resolver.ts
|
|
8521
8587
|
var VARIABLE_SCOPES = ["local", "data", "environment", "folder", "collection", "global"];
|
|
8522
8588
|
var MAX_VARIABLE_DEPTH = 10;
|
|
@@ -8565,6 +8631,12 @@ var VariableResolver = class {
|
|
|
8565
8631
|
unresolved = /* @__PURE__ */ new Set();
|
|
8566
8632
|
/** Ids of secrets that were referenced and not given. */
|
|
8567
8633
|
lockedSecrets = /* @__PURE__ */ new Set();
|
|
8634
|
+
/**
|
|
8635
|
+
* Each variable whose value was a secret that was given, by name, with the
|
|
8636
|
+
* scope it was found in. A reference written into a text itself is named
|
|
8637
|
+
* as written, with no scope.
|
|
8638
|
+
*/
|
|
8639
|
+
vaulted = /* @__PURE__ */ new Map();
|
|
8568
8640
|
scopes;
|
|
8569
8641
|
/** `text` with its variables replaced. Throws `VariableNestingError`. */
|
|
8570
8642
|
resolve(text3) {
|
|
@@ -8577,7 +8649,8 @@ var VariableResolver = class {
|
|
|
8577
8649
|
...this.tokensOf(row.value, "header", `header-${index}-value`)
|
|
8578
8650
|
]);
|
|
8579
8651
|
const body3 = (payload.body ?? []).flatMap((text3, index) => this.tokensOf(text3, "body", `body-${index}`));
|
|
8580
|
-
|
|
8652
|
+
const auth3 = (payload.auth ?? []).flatMap((text3, index) => this.tokensOf(text3, "auth", `auth-${index}`));
|
|
8653
|
+
return [...this.tokensOf(payload.url ?? "", "url", "endpoint"), ...header, ...body3, ...auth3];
|
|
8581
8654
|
}
|
|
8582
8655
|
tokensOf(text3, location, field4) {
|
|
8583
8656
|
const names = new Set([...text3.matchAll(PLACEHOLDER)].map((match) => match[1].trim()));
|
|
@@ -8588,6 +8661,7 @@ var VariableResolver = class {
|
|
|
8588
8661
|
if (DYNAMIC.has(name)) return { source: "dynamic" };
|
|
8589
8662
|
const found = this.find(name);
|
|
8590
8663
|
if (!found) return { source: "missing" };
|
|
8664
|
+
if (SECRET_REFERENCE.test(found.value)) return { source: found.scope, vaulted: true };
|
|
8591
8665
|
try {
|
|
8592
8666
|
return { source: found.scope, value: this.expand(found.value, [name]) };
|
|
8593
8667
|
} catch (error) {
|
|
@@ -8614,6 +8688,8 @@ var VariableResolver = class {
|
|
|
8614
8688
|
return written;
|
|
8615
8689
|
}
|
|
8616
8690
|
this.taint.add(plaintext);
|
|
8691
|
+
const holder = chain.at(-1);
|
|
8692
|
+
this.vaulted.set(holder ?? written, holder === void 0 ? void 0 : this.find(holder)?.scope);
|
|
8617
8693
|
return plaintext;
|
|
8618
8694
|
}
|
|
8619
8695
|
const dynamic = DYNAMIC.get(name);
|
|
@@ -8644,6 +8720,80 @@ function withJarCookies(headers2, cookies) {
|
|
|
8644
8720
|
return headers2.map((header, index) => index === at ? [header[0], `${header[1]}; ${cookieHeader(added2)}`] : header);
|
|
8645
8721
|
}
|
|
8646
8722
|
|
|
8723
|
+
// ../core/src/send/record.ts
|
|
8724
|
+
function formatDuration(ms) {
|
|
8725
|
+
if (ms === void 0 || ms === null || !(ms >= 0)) return "\u2014";
|
|
8726
|
+
if (ms > 0 && ms < 0.5) return "<1 ms";
|
|
8727
|
+
if (Math.round(ms) < 1e3) return `${Math.round(ms)} ms`;
|
|
8728
|
+
return `${(ms / 1e3).toFixed(1)} s`;
|
|
8729
|
+
}
|
|
8730
|
+
var isFields = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8731
|
+
var SCOPE_NAMES = VARIABLE_SCOPES;
|
|
8732
|
+
var FAILURE_KINDS = ["network", "timeout", "aborted", "bridge"];
|
|
8733
|
+
function recordProblems(value, path) {
|
|
8734
|
+
const problems = [];
|
|
8735
|
+
const object = (at, held, fields) => {
|
|
8736
|
+
if (!isFields(held)) {
|
|
8737
|
+
problems.push(`${at}: Value must be an object.`);
|
|
8738
|
+
return;
|
|
8739
|
+
}
|
|
8740
|
+
for (const key of Object.keys(held))
|
|
8741
|
+
if (!Object.hasOwn(fields, key)) problems.push(`${at}.${key}: The app writes no such field.`);
|
|
8742
|
+
for (const [key, check] of Object.entries(fields)) {
|
|
8743
|
+
const field4 = Object.hasOwn(held, key) ? held[key] : void 0;
|
|
8744
|
+
if (!check(field4, `${at}.${key}`)) problems.push(`${at}.${key}: Value is not one the app writes.`);
|
|
8745
|
+
}
|
|
8746
|
+
};
|
|
8747
|
+
const optional = (check) => (field4, where) => field4 === void 0 || check(field4, where);
|
|
8748
|
+
const text3 = (field4) => typeof field4 === "string";
|
|
8749
|
+
const yes = (field4) => field4 === true;
|
|
8750
|
+
const flag2 = (field4) => typeof field4 === "boolean";
|
|
8751
|
+
const texts = (field4) => Array.isArray(field4) && field4.every(text3);
|
|
8752
|
+
const nested = (fields) => (field4, where) => {
|
|
8753
|
+
object(where, field4, fields);
|
|
8754
|
+
return true;
|
|
8755
|
+
};
|
|
8756
|
+
object(path, value, {
|
|
8757
|
+
status: optional(
|
|
8758
|
+
nested({
|
|
8759
|
+
code: (field4) => Number.isInteger(field4) && field4 >= 0 && field4 <= 999,
|
|
8760
|
+
text: text3
|
|
8761
|
+
})
|
|
8762
|
+
),
|
|
8763
|
+
durationMs: (field4) => typeof field4 === "number" && Number.isFinite(field4) && field4 >= 0,
|
|
8764
|
+
route: (field4) => field4 === "direct" || field4 === "bridge",
|
|
8765
|
+
unpaired: optional(yes),
|
|
8766
|
+
host: text3,
|
|
8767
|
+
secrets: (field4, where) => {
|
|
8768
|
+
if (!Array.isArray(field4)) return false;
|
|
8769
|
+
field4.forEach(
|
|
8770
|
+
(secret, index) => object(`${where}[${index}]`, secret, {
|
|
8771
|
+
name: text3,
|
|
8772
|
+
scope: optional((scope) => typeof scope === "string" && SCOPE_NAMES.includes(scope)),
|
|
8773
|
+
holder: optional(text3)
|
|
8774
|
+
})
|
|
8775
|
+
);
|
|
8776
|
+
return true;
|
|
8777
|
+
},
|
|
8778
|
+
masked: nested({
|
|
8779
|
+
address: optional(yes),
|
|
8780
|
+
headers: texts,
|
|
8781
|
+
body: optional(yes),
|
|
8782
|
+
responseHeaders: texts,
|
|
8783
|
+
responseBody: optional(yes)
|
|
8784
|
+
}),
|
|
8785
|
+
kept: nested({ response: flag2, body: flag2, quoteLeftOut: optional(yes) }),
|
|
8786
|
+
failure: optional(
|
|
8787
|
+
nested({
|
|
8788
|
+
kind: (field4) => typeof field4 === "string" && FAILURE_KINDS.includes(field4),
|
|
8789
|
+
reported: text3,
|
|
8790
|
+
summary: text3
|
|
8791
|
+
})
|
|
8792
|
+
)
|
|
8793
|
+
});
|
|
8794
|
+
return problems;
|
|
8795
|
+
}
|
|
8796
|
+
|
|
8647
8797
|
// ../core/src/transport/sse.ts
|
|
8648
8798
|
var SseParser = class {
|
|
8649
8799
|
rest = "";
|
|
@@ -8692,13 +8842,15 @@ var SseParser = class {
|
|
|
8692
8842
|
// ../core/src/transport/transport.ts
|
|
8693
8843
|
var STREAM_BODY_BYTES = 1024 * 1024;
|
|
8694
8844
|
var TransportError = class extends Error {
|
|
8695
|
-
constructor(kind, message, status) {
|
|
8845
|
+
constructor(kind, message, status, responded = false) {
|
|
8696
8846
|
super(message);
|
|
8697
8847
|
this.kind = kind;
|
|
8698
8848
|
this.status = status;
|
|
8849
|
+
this.responded = responded;
|
|
8699
8850
|
}
|
|
8700
8851
|
kind;
|
|
8701
8852
|
status;
|
|
8853
|
+
responded;
|
|
8702
8854
|
name = "TransportError";
|
|
8703
8855
|
};
|
|
8704
8856
|
var DISPLAY_CAP_BYTES = 50 * 1024 * 1024;
|
|
@@ -8708,23 +8860,30 @@ async function withDeadline(options, run) {
|
|
|
8708
8860
|
const cancel = () => controller.abort();
|
|
8709
8861
|
if (options.signal.aborted) cancel();
|
|
8710
8862
|
options.signal.addEventListener("abort", cancel);
|
|
8711
|
-
const timer = options.timeoutMs > 0 ? setTimeout(
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8863
|
+
const timer = options.timeoutMs > 0 ? setTimeout(
|
|
8864
|
+
() => {
|
|
8865
|
+
timedOut = true;
|
|
8866
|
+
controller.abort();
|
|
8867
|
+
},
|
|
8868
|
+
Math.min(options.timeoutMs, 2 ** 31 - 1)
|
|
8869
|
+
) : void 0;
|
|
8715
8870
|
try {
|
|
8716
8871
|
return await run(controller.signal, () => clearTimeout(timer));
|
|
8717
8872
|
} catch (cause) {
|
|
8718
8873
|
if (cause instanceof TransportError) throw cause;
|
|
8719
|
-
if (timedOut)
|
|
8720
|
-
|
|
8874
|
+
if (timedOut)
|
|
8875
|
+
throw new TransportError(
|
|
8876
|
+
"timeout",
|
|
8877
|
+
`No response within ${formatDuration(options.timeoutMs)}, the request timeout.`
|
|
8878
|
+
);
|
|
8879
|
+
if (options.signal.aborted) throw new TransportError("aborted", "You stopped the request.");
|
|
8721
8880
|
throw new TransportError("network", networkMessage(cause));
|
|
8722
8881
|
} finally {
|
|
8723
8882
|
clearTimeout(timer);
|
|
8724
8883
|
options.signal.removeEventListener("abort", cancel);
|
|
8725
8884
|
}
|
|
8726
8885
|
}
|
|
8727
|
-
async function
|
|
8886
|
+
async function readBody2(response, cap = DISPLAY_CAP_BYTES) {
|
|
8728
8887
|
if (!response.body) return response.arrayBuffer();
|
|
8729
8888
|
const reader2 = response.body.getReader();
|
|
8730
8889
|
let chunks = [];
|
|
@@ -8758,7 +8917,7 @@ async function readEventStream(response, signal, onEvent) {
|
|
|
8758
8917
|
const kept = new Uint8Array(STREAM_BODY_BYTES);
|
|
8759
8918
|
let size = 0;
|
|
8760
8919
|
let truncated = false;
|
|
8761
|
-
let
|
|
8920
|
+
let stopped2 = false;
|
|
8762
8921
|
const reader2 = response.body?.getReader();
|
|
8763
8922
|
try {
|
|
8764
8923
|
for (; ; ) {
|
|
@@ -8768,13 +8927,14 @@ async function readEventStream(response, signal, onEvent) {
|
|
|
8768
8927
|
kept.set(chunk.value.subarray(0, room), size);
|
|
8769
8928
|
size += Math.min(room, chunk.value.byteLength);
|
|
8770
8929
|
if (chunk.value.byteLength > room) truncated = true;
|
|
8771
|
-
for (const event of parser.push(decoder.decode(chunk.value, { stream: true })))
|
|
8930
|
+
for (const event of parser.push(decoder.decode(chunk.value, { stream: true })))
|
|
8931
|
+
onEvent({ ...event, timeMs: Date.now() - started });
|
|
8772
8932
|
}
|
|
8773
8933
|
} catch (error) {
|
|
8774
8934
|
if (!signal.aborted) throw error;
|
|
8775
|
-
|
|
8935
|
+
stopped2 = true;
|
|
8776
8936
|
}
|
|
8777
|
-
return { body: kept.slice(0, size).buffer, stream: { stopped, ...truncated && { truncated } } };
|
|
8937
|
+
return { body: kept.slice(0, size).buffer, stream: { stopped: stopped2, ...truncated && { truncated } } };
|
|
8778
8938
|
}
|
|
8779
8939
|
function networkMessage(cause) {
|
|
8780
8940
|
if (!(cause instanceof Error)) return String(cause);
|
|
@@ -8791,7 +8951,8 @@ var FetchTransport = class {
|
|
|
8791
8951
|
fetchFn;
|
|
8792
8952
|
displayCap;
|
|
8793
8953
|
send(request, options) {
|
|
8794
|
-
|
|
8954
|
+
let responded = false;
|
|
8955
|
+
const sent = withDeadline(options, async (signal, arrived) => {
|
|
8795
8956
|
const response = await this.fetchFn(request.url, {
|
|
8796
8957
|
method: request.method,
|
|
8797
8958
|
headers: request.headers,
|
|
@@ -8802,9 +8963,10 @@ var FetchTransport = class {
|
|
|
8802
8963
|
redirect: "follow",
|
|
8803
8964
|
signal
|
|
8804
8965
|
});
|
|
8966
|
+
responded = true;
|
|
8805
8967
|
const { onEvent } = options;
|
|
8806
8968
|
const events = onEvent && /^text\/event-stream\b/i.test(response.headers.get("content-type") ?? "") ? (arrived(), await readEventStream(response, signal, onEvent)) : void 0;
|
|
8807
|
-
const body3 = events?.body ?? await
|
|
8969
|
+
const body3 = events?.body ?? await readBody2(response, this.displayCap);
|
|
8808
8970
|
return {
|
|
8809
8971
|
status: response.status,
|
|
8810
8972
|
statusText: response.statusText,
|
|
@@ -8817,6 +8979,11 @@ var FetchTransport = class {
|
|
|
8817
8979
|
...events && { stream: events.stream }
|
|
8818
8980
|
};
|
|
8819
8981
|
});
|
|
8982
|
+
return sent.catch((error) => {
|
|
8983
|
+
if (responded && error instanceof TransportError && error.kind === "network")
|
|
8984
|
+
throw new TransportError("network", error.message, void 0, true);
|
|
8985
|
+
throw error;
|
|
8986
|
+
});
|
|
8820
8987
|
}
|
|
8821
8988
|
};
|
|
8822
8989
|
|
|
@@ -9519,7 +9686,7 @@ var _HMAC = class {
|
|
|
9519
9686
|
this.iHash.destroy();
|
|
9520
9687
|
}
|
|
9521
9688
|
};
|
|
9522
|
-
var
|
|
9689
|
+
var hmac = /* @__PURE__ */ (() => {
|
|
9523
9690
|
const hmac_ = ((hash, key, message) => new _HMAC(hash, key).update(message).digest());
|
|
9524
9691
|
hmac_.create = (hash, key) => new _HMAC(hash, key);
|
|
9525
9692
|
return hmac_;
|
|
@@ -9963,18 +10130,19 @@ var _SHA256 = class extends SHA2_32B {
|
|
|
9963
10130
|
super(32, SHA256_IV);
|
|
9964
10131
|
}
|
|
9965
10132
|
};
|
|
9966
|
-
var
|
|
10133
|
+
var sha256 = /* @__PURE__ */ createHasher(
|
|
9967
10134
|
() => new _SHA256(),
|
|
9968
10135
|
/* @__PURE__ */ oidNist(1)
|
|
9969
10136
|
);
|
|
9970
10137
|
|
|
9971
10138
|
// ../core/src/scripting/vm-crypto.ts
|
|
9972
|
-
var HASHES = { MD5: md5, SHA1: sha1, SHA256:
|
|
10139
|
+
var HASHES = { MD5: md5, SHA1: sha1, SHA256: sha256 };
|
|
9973
10140
|
var isVmHash = (name) => Object.hasOwn(HASHES, name);
|
|
9974
10141
|
var VM_ENCODINGS = ["utf8", "hex", "base64"];
|
|
9975
10142
|
function fromWords(json) {
|
|
9976
10143
|
const list4 = JSON.parse(json);
|
|
9977
|
-
if (!Array.isArray(list4) || !list4.every((entry) => typeof entry === "number"))
|
|
10144
|
+
if (!Array.isArray(list4) || !list4.every((entry) => typeof entry === "number"))
|
|
10145
|
+
throw new TypeError("Expected a WordArray.");
|
|
9978
10146
|
const [length, ...words] = list4;
|
|
9979
10147
|
const bytes = new Uint8Array(Math.max(0, Math.min(length, words.length * 4)));
|
|
9980
10148
|
for (let i = 0; i < bytes.length; i++) bytes[i] = words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
|
|
@@ -9994,11 +10162,14 @@ function vmHash(name, format, payload) {
|
|
|
9994
10162
|
return toWords(HASHES[name](bytesOf(format, payload)));
|
|
9995
10163
|
}
|
|
9996
10164
|
function vmHmac(name, keyFormat, key, format, payload) {
|
|
9997
|
-
return toWords(
|
|
10165
|
+
return toWords(hmac(HASHES[name], bytesOf(keyFormat, key), bytesOf(format, payload)));
|
|
9998
10166
|
}
|
|
9999
10167
|
function vmEncode(encoding, text3) {
|
|
10000
10168
|
if (encoding === "utf8") return toWords(new TextEncoder().encode(text3));
|
|
10001
|
-
if (encoding === "hex")
|
|
10169
|
+
if (encoding === "hex")
|
|
10170
|
+
return toWords(
|
|
10171
|
+
Uint8Array.from({ length: text3.length >>> 1 }, (_, i) => parseInt(text3.slice(i * 2, i * 2 + 2), 16) || 0)
|
|
10172
|
+
);
|
|
10002
10173
|
return toWords(Uint8Array.from(atob(text3.replace(/=+$/, "")), (char) => char.charCodeAt(0)));
|
|
10003
10174
|
}
|
|
10004
10175
|
function vmDecode(encoding, words) {
|
|
@@ -10014,7 +10185,9 @@ function vmDecode(encoding, words) {
|
|
|
10014
10185
|
}
|
|
10015
10186
|
function vmRandom(count) {
|
|
10016
10187
|
if (!Number.isInteger(count) || count < 0 || count > 65536) throw new RangeError("Expected up to 65536 bytes.");
|
|
10017
|
-
return Array.from(crypto.getRandomValues(new Uint8Array(count)), (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
10188
|
+
return Array.from(crypto.getRandomValues(new Uint8Array(count)), (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
10189
|
+
""
|
|
10190
|
+
);
|
|
10018
10191
|
}
|
|
10019
10192
|
|
|
10020
10193
|
// ../core/src/scripting/host.ts
|
|
@@ -10039,7 +10212,10 @@ var SCRIPT_LIMITS = {
|
|
|
10039
10212
|
var WASM_PAGE = 65536;
|
|
10040
10213
|
var ENGINE_INITIAL_PAGES = 256;
|
|
10041
10214
|
function scriptMemory(limits = SCRIPT_LIMITS) {
|
|
10042
|
-
return {
|
|
10215
|
+
return {
|
|
10216
|
+
initial: ENGINE_INITIAL_PAGES,
|
|
10217
|
+
maximum: Math.max(ENGINE_INITIAL_PAGES, Math.floor(limits.memoryBytes / WASM_PAGE))
|
|
10218
|
+
};
|
|
10043
10219
|
}
|
|
10044
10220
|
var LOG_TRUNCATED = "[console output truncated]";
|
|
10045
10221
|
function emptyChanges() {
|
|
@@ -10054,9 +10230,30 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
|
|
|
10054
10230
|
const scope = (rows2 = []) => new Map(rows2.map(([name, value]) => [name, value]));
|
|
10055
10231
|
const marked = (rows2 = []) => new Set(rows2.filter(([, value, json]) => json === true && isJsonText(value)).map(([name]) => name));
|
|
10056
10232
|
const state = {
|
|
10057
|
-
values: {
|
|
10058
|
-
|
|
10059
|
-
|
|
10233
|
+
values: {
|
|
10234
|
+
local: scope(),
|
|
10235
|
+
data: scope(context.data),
|
|
10236
|
+
environment: scope(context.environment),
|
|
10237
|
+
folder: scope(context.folder),
|
|
10238
|
+
collection: scope(context.collection),
|
|
10239
|
+
global: scope(context.globals)
|
|
10240
|
+
},
|
|
10241
|
+
json: {
|
|
10242
|
+
local: marked(),
|
|
10243
|
+
data: marked(context.data),
|
|
10244
|
+
environment: marked(context.environment),
|
|
10245
|
+
folder: marked(context.folder),
|
|
10246
|
+
collection: marked(context.collection),
|
|
10247
|
+
global: marked(context.globals)
|
|
10248
|
+
},
|
|
10249
|
+
changes: {
|
|
10250
|
+
local: /* @__PURE__ */ new Map(),
|
|
10251
|
+
data: /* @__PURE__ */ new Map(),
|
|
10252
|
+
environment: /* @__PURE__ */ new Map(),
|
|
10253
|
+
folder: /* @__PURE__ */ new Map(),
|
|
10254
|
+
collection: /* @__PURE__ */ new Map(),
|
|
10255
|
+
global: /* @__PURE__ */ new Map()
|
|
10256
|
+
},
|
|
10060
10257
|
sends: 0,
|
|
10061
10258
|
inFlight: 0,
|
|
10062
10259
|
answers: [],
|
|
@@ -10066,7 +10263,11 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
|
|
|
10066
10263
|
tests: [],
|
|
10067
10264
|
timers: /* @__PURE__ */ new Map()
|
|
10068
10265
|
};
|
|
10069
|
-
const changed = (name) => [...state.changes[name]].map(([key, value]) => ({
|
|
10266
|
+
const changed = (name) => [...state.changes[name]].map(([key, value]) => ({
|
|
10267
|
+
key,
|
|
10268
|
+
value,
|
|
10269
|
+
...value !== null && state.json[name].has(key) && { json: true }
|
|
10270
|
+
}));
|
|
10070
10271
|
const result = (error, limit) => ({
|
|
10071
10272
|
logs: state.logs,
|
|
10072
10273
|
changes: { environment: changed("environment"), collection: changed("collection"), global: changed("global") },
|
|
@@ -10086,7 +10287,8 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
|
|
|
10086
10287
|
try {
|
|
10087
10288
|
failure = await evaluate(vm, runtime, source, context, state, deadline, limits, services);
|
|
10088
10289
|
} catch (error) {
|
|
10089
|
-
if (error instanceof RangeError || error instanceof Error && error.name === "InternalError")
|
|
10290
|
+
if (error instanceof RangeError || error instanceof Error && error.name === "InternalError")
|
|
10291
|
+
return result(void 0, "stack");
|
|
10090
10292
|
throw error;
|
|
10091
10293
|
}
|
|
10092
10294
|
vm.dispose();
|
|
@@ -10099,8 +10301,17 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10099
10301
|
if (Date.now() >= deadline.at) return { message: "", limit: "timeout" };
|
|
10100
10302
|
const kind = vm.typeof(error);
|
|
10101
10303
|
if (kind === "string") return { message: vm.getString(error) };
|
|
10304
|
+
if (kind === "number" || kind === "boolean" || kind === "undefined") return { message: String(vm.dump(error)) };
|
|
10102
10305
|
if (kind !== "object") return { message: "Script failed." };
|
|
10103
|
-
if (vm.sameValue(error, vm.null))
|
|
10306
|
+
if (vm.sameValue(error, vm.null)) {
|
|
10307
|
+
const probe = vm.evalCode("({})");
|
|
10308
|
+
if (probe.error) {
|
|
10309
|
+
probe.error.dispose();
|
|
10310
|
+
return { message: "", limit: "memory" };
|
|
10311
|
+
}
|
|
10312
|
+
probe.value.dispose();
|
|
10313
|
+
return { message: "null" };
|
|
10314
|
+
}
|
|
10104
10315
|
const name = property(vm, error, "name");
|
|
10105
10316
|
const message = property(vm, error, "message") ?? "Script failed.";
|
|
10106
10317
|
if (name === "InternalError" && message === "out of memory") return { message, limit: "memory" };
|
|
@@ -10130,7 +10341,8 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10130
10341
|
};
|
|
10131
10342
|
const charge = (...written) => {
|
|
10132
10343
|
state.written += written.reduce((sum, part) => sum + part.length, 0);
|
|
10133
|
-
if (state.written > limits.maxOutputBytes)
|
|
10344
|
+
if (state.written > limits.maxOutputBytes)
|
|
10345
|
+
throw new RangeError("Script wrote too many test results and variables.");
|
|
10134
10346
|
};
|
|
10135
10347
|
const scopeOf = (handle) => {
|
|
10136
10348
|
const name = text3(handle);
|
|
@@ -10153,7 +10365,9 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10153
10365
|
});
|
|
10154
10366
|
bind("varHas", (where, key) => lookUp(where, text3(key)) === void 0 ? vm.false : vm.true);
|
|
10155
10367
|
bind("varAll", (where) => {
|
|
10156
|
-
const of2 = (name) => [...state.values[name]].map(
|
|
10368
|
+
const of2 = (name) => [...state.values[name]].map(
|
|
10369
|
+
([key, value]) => `[${JSON.stringify(key)},${state.json[name].has(key) ? value : JSON.stringify(value)}]`
|
|
10370
|
+
);
|
|
10157
10371
|
const entries = any(where) ? [...VARIABLE_ORDER].reverse().flatMap(of2) : of2(scopeOf(where));
|
|
10158
10372
|
return vm.newString(`[${entries.join(",")}]`);
|
|
10159
10373
|
});
|
|
@@ -10219,7 +10433,8 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10219
10433
|
const id = text3(name);
|
|
10220
10434
|
const source2 = services.libraries?.get(id);
|
|
10221
10435
|
if (source2 !== void 0) return vm.newString(source2);
|
|
10222
|
-
if (VM_LIBRARIES.includes(id))
|
|
10436
|
+
if (VM_LIBRARIES.includes(id))
|
|
10437
|
+
throw new Error(`require('${id}'): write the module's name out in the script, as require('${id}')`);
|
|
10223
10438
|
return void 0;
|
|
10224
10439
|
});
|
|
10225
10440
|
const hash = (handle) => {
|
|
@@ -10234,7 +10449,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10234
10449
|
return found;
|
|
10235
10450
|
};
|
|
10236
10451
|
bind("hash", (name, format, payload) => vm.newString(vmHash(hash(name), text3(format), text3(payload))));
|
|
10237
|
-
bind(
|
|
10452
|
+
bind(
|
|
10453
|
+
"hmac",
|
|
10454
|
+
(name, keyFormat, key, format, payload) => vm.newString(vmHmac(hash(name), text3(keyFormat), text3(key), text3(format), text3(payload)))
|
|
10455
|
+
);
|
|
10238
10456
|
bind("encode", (name, value) => vm.newString(vmEncode(encoding(name), text3(value))));
|
|
10239
10457
|
bind("decode", (name, words) => vm.newString(vmDecode(encoding(name), text3(words))));
|
|
10240
10458
|
bind("random", (count) => vm.newString(vmRandom(number(count))));
|
|
@@ -10258,7 +10476,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10258
10476
|
state.answers.push([call, ok, JSON.stringify(value)]);
|
|
10259
10477
|
state.wake?.();
|
|
10260
10478
|
};
|
|
10261
|
-
const refused = (error) => ({
|
|
10479
|
+
const refused = (error) => ({
|
|
10480
|
+
name: error instanceof Error ? error.name : "Error",
|
|
10481
|
+
message: error instanceof Error ? error.message : String(error)
|
|
10482
|
+
});
|
|
10262
10483
|
bind("sendFailed", (call, name, message) => {
|
|
10263
10484
|
answer(number(call), false, { name: text3(name), message: text3(message) });
|
|
10264
10485
|
return void 0;
|
|
@@ -10267,7 +10488,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10267
10488
|
const [id, body3] = [number(call), text3(json)];
|
|
10268
10489
|
state.sends++;
|
|
10269
10490
|
if (state.sends > limits.maxSendRequests) {
|
|
10270
|
-
answer(id, false, {
|
|
10491
|
+
answer(id, false, {
|
|
10492
|
+
name: "Error",
|
|
10493
|
+
message: `pm.sendRequest: a script may make ${limits.maxSendRequests} requests, and this is one more.`
|
|
10494
|
+
});
|
|
10271
10495
|
return void 0;
|
|
10272
10496
|
}
|
|
10273
10497
|
const send = services.send;
|
|
@@ -10370,12 +10594,15 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
|
|
|
10370
10594
|
}
|
|
10371
10595
|
}
|
|
10372
10596
|
var VARIABLE_ORDER = VARIABLE_SCOPES;
|
|
10373
|
-
var isPairs = (value) => Array.isArray(value) && value.every(
|
|
10597
|
+
var isPairs = (value) => Array.isArray(value) && value.every(
|
|
10598
|
+
(pair) => Array.isArray(pair) && pair.length === 2 && typeof pair[0] === "string" && typeof pair[1] === "string"
|
|
10599
|
+
);
|
|
10374
10600
|
function parsed(json) {
|
|
10375
10601
|
const value = JSON.parse(json);
|
|
10376
10602
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Expected a request.");
|
|
10377
10603
|
const request = value;
|
|
10378
|
-
if (typeof request["method"] !== "string" || !isHttpMethod(request["method"]))
|
|
10604
|
+
if (typeof request["method"] !== "string" || !isHttpMethod(request["method"]))
|
|
10605
|
+
throw new TypeError("The request's method is not an HTTP method.");
|
|
10379
10606
|
if (typeof request["url"] !== "string" || !request["url"].trim()) throw new TypeError("The request has no address.");
|
|
10380
10607
|
if (!isPairs(request["headers"])) throw new TypeError("The request's headers are not a list of names and values.");
|
|
10381
10608
|
return request;
|
|
@@ -10384,15 +10611,26 @@ function sendRequest(json) {
|
|
|
10384
10611
|
const request = parsed(json);
|
|
10385
10612
|
const body3 = request["body"];
|
|
10386
10613
|
if (body3 !== void 0 && typeof body3 !== "string") throw new TypeError("The request's body is not text.");
|
|
10387
|
-
return {
|
|
10614
|
+
return {
|
|
10615
|
+
method: request["method"],
|
|
10616
|
+
url: request["url"],
|
|
10617
|
+
headers: request["headers"],
|
|
10618
|
+
...body3 !== void 0 && { body: body3 }
|
|
10619
|
+
};
|
|
10388
10620
|
}
|
|
10389
10621
|
function scriptRequest(json) {
|
|
10390
10622
|
const request = parsed(json);
|
|
10391
10623
|
const body3 = request["body"];
|
|
10392
|
-
const base = {
|
|
10624
|
+
const base = {
|
|
10625
|
+
method: request["method"],
|
|
10626
|
+
url: request["url"],
|
|
10627
|
+
headers: request["headers"]
|
|
10628
|
+
};
|
|
10393
10629
|
if (body3?.mode === "raw" && typeof body3.raw === "string") return { ...base, body: { mode: "raw", raw: body3.raw } };
|
|
10394
|
-
if (body3?.mode === "urlencoded" && isPairs(body3.urlencoded))
|
|
10395
|
-
|
|
10630
|
+
if (body3?.mode === "urlencoded" && isPairs(body3.urlencoded))
|
|
10631
|
+
return { ...base, body: { mode: "urlencoded", urlencoded: body3.urlencoded } };
|
|
10632
|
+
if (body3?.mode === "none" || body3?.mode === "multipart" || body3?.mode === "binary")
|
|
10633
|
+
return { ...base, body: { mode: body3.mode } };
|
|
10396
10634
|
throw new TypeError("The request's body is not one a script can set.");
|
|
10397
10635
|
}
|
|
10398
10636
|
function property(vm, object, name) {
|
|
@@ -10404,25 +10642,6 @@ function property(vm, object, name) {
|
|
|
10404
10642
|
}
|
|
10405
10643
|
}
|
|
10406
10644
|
|
|
10407
|
-
// ../core/src/scripting/request-view.ts
|
|
10408
|
-
function scriptRequestOf(content) {
|
|
10409
|
-
const rows2 = (list4) => list4.filter((row) => row.enabled && row.key).map((row) => [row.key, row.value]);
|
|
10410
|
-
const { body: body3 } = content;
|
|
10411
|
-
const seen = body3.mode === "raw" ? { mode: "raw", raw: body3.raw?.text ?? "" } : body3.mode === "graphql" ? { mode: "raw", raw: graphqlText(body3.graphql?.query ?? "", body3.graphql?.variables ?? "") } : body3.mode === "urlencoded" ? { mode: "urlencoded", urlencoded: rows2(body3.urlencoded ?? []) } : { mode: body3.mode };
|
|
10412
|
-
return { method: content.method, url: content.url, headers: rows2(content.headers), body: seen };
|
|
10413
|
-
}
|
|
10414
|
-
function withScriptRequest(content, request) {
|
|
10415
|
-
const rows2 = (pairs2) => pairs2.map(([key, value]) => ({ key, value, enabled: true }));
|
|
10416
|
-
const untouched = content.body.mode === "graphql" && request.body.mode === "raw" && request.body.raw === graphqlText(content.body.graphql?.query ?? "", content.body.graphql?.variables ?? "");
|
|
10417
|
-
const body3 = untouched ? content.body : request.body.mode === "raw" ? { ...content.body, mode: "raw", raw: { language: content.body.mode === "graphql" ? "json" : content.body.raw?.language ?? "text", text: request.body.raw } } : request.body.mode === "urlencoded" ? { ...content.body, mode: "urlencoded", urlencoded: rows2(request.body.urlencoded) } : content.body;
|
|
10418
|
-
return { ...content, method: request.method, url: request.url, headers: rows2(request.headers), body: body3 };
|
|
10419
|
-
}
|
|
10420
|
-
function sentScriptRequest(content, sent) {
|
|
10421
|
-
const { mode } = content.body;
|
|
10422
|
-
const body3 = mode === "multipart" || mode === "binary" ? { mode } : sent.body === void 0 ? { mode: "none" } : mode === "urlencoded" ? { mode, urlencoded: [...new URLSearchParams(sent.body)] } : { mode: "raw", raw: sent.body };
|
|
10423
|
-
return { method: sent.method, url: sent.url, headers: sent.headers, body: body3 };
|
|
10424
|
-
}
|
|
10425
|
-
|
|
10426
10645
|
// ../core/src/scripting/trust.ts
|
|
10427
10646
|
function scriptsOf(holder) {
|
|
10428
10647
|
return [holder.scripts.pre, holder.scripts.post].filter((script) => script.trim());
|
|
@@ -10434,7 +10653,8 @@ function runFailed(result) {
|
|
|
10434
10653
|
}
|
|
10435
10654
|
function requestsInOrder(tree, folderId) {
|
|
10436
10655
|
const entered = /* @__PURE__ */ new Set();
|
|
10437
|
-
const
|
|
10656
|
+
const childrenOf = childrenInOrder(tree);
|
|
10657
|
+
const inOrder = (parent) => childrenOf(parent).flatMap((child) => {
|
|
10438
10658
|
if (child.request) return [child.request];
|
|
10439
10659
|
if (entered.has(child.folder.id)) return [];
|
|
10440
10660
|
entered.add(child.folder.id);
|
|
@@ -10457,7 +10677,12 @@ async function runRequests(requests, send, options = {}) {
|
|
|
10457
10677
|
const count = options.iterations ?? (data.length || 1);
|
|
10458
10678
|
const signal = options.signal ?? new AbortController().signal;
|
|
10459
10679
|
const results = [];
|
|
10460
|
-
const report = (ended, reason) => ({
|
|
10680
|
+
const report = (ended, reason) => ({
|
|
10681
|
+
iterations: count,
|
|
10682
|
+
results,
|
|
10683
|
+
ended,
|
|
10684
|
+
...reason !== void 0 && { reason }
|
|
10685
|
+
});
|
|
10461
10686
|
let first = true;
|
|
10462
10687
|
for (let index = 0; index < count; index++) {
|
|
10463
10688
|
const iteration = { index, count, data: data[Math.min(index, data.length - 1)] ?? [] };
|
|
@@ -10479,31 +10704,59 @@ async function runRequests(requests, send, options = {}) {
|
|
|
10479
10704
|
return report("completed");
|
|
10480
10705
|
}
|
|
10481
10706
|
|
|
10707
|
+
// ../core/src/run/words.ts
|
|
10708
|
+
function runEnding(report) {
|
|
10709
|
+
switch (report.ended) {
|
|
10710
|
+
case "completed":
|
|
10711
|
+
return "The run is complete.";
|
|
10712
|
+
case "cancelled":
|
|
10713
|
+
return "You stopped the run.";
|
|
10714
|
+
case "failed":
|
|
10715
|
+
return "The run stopped at the first failure.";
|
|
10716
|
+
case "ended":
|
|
10717
|
+
return `The run was stopped. ${report.reason ?? ""}`.trim();
|
|
10718
|
+
}
|
|
10719
|
+
}
|
|
10720
|
+
function runCounts(report) {
|
|
10721
|
+
const { results, iterations } = report;
|
|
10722
|
+
const tests2 = results.flatMap((result) => result.tests);
|
|
10723
|
+
const failed = results.filter(runFailed).length;
|
|
10724
|
+
const passed = tests2.filter((test) => test.passed).length;
|
|
10725
|
+
return `${results.length} ${results.length === 1 ? "result" : "results"} in ${iterations} ${iterations === 1 ? "iteration" : "iterations"}; ${failed} failed; ${passed} of ${tests2.length} tests passed.`;
|
|
10726
|
+
}
|
|
10727
|
+
function sharedErrors(results) {
|
|
10728
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
10729
|
+
for (const { error } of results) if (error !== void 0) counts2.set(error, (counts2.get(error) ?? 0) + 1);
|
|
10730
|
+
return [...counts2].filter(([, count]) => count >= 2).sort((a, b) => b[1] - a[1]).map(([error, count]) => ({ error, count }));
|
|
10731
|
+
}
|
|
10732
|
+
function sharedErrorHeading(count) {
|
|
10733
|
+
return `${count} requests failed with the same error.`;
|
|
10734
|
+
}
|
|
10735
|
+
|
|
10482
10736
|
// ../core/src/send/wire.ts
|
|
10483
10737
|
var SECRET_PLACEHOLDER = /\{\{\s*\$secret\./;
|
|
10484
10738
|
function containsSecretPlaceholder(request) {
|
|
10485
10739
|
const wire = [
|
|
10486
10740
|
request.url,
|
|
10487
|
-
|
|
10741
|
+
formDecoded(request.url),
|
|
10488
10742
|
JSON.stringify(request.headers),
|
|
10489
10743
|
...request.headers.map(([, value]) => decodeBasicCredentials(value)),
|
|
10490
10744
|
// An OAuth 2.0 token request names its client in Basic credentials that are form-encoded first (RFC 6749, 2.3.1).
|
|
10491
|
-
...request.headers.map(([, value]) =>
|
|
10745
|
+
...request.headers.map(([, value]) => formDecoded(decodeBasicCredentials(value))),
|
|
10492
10746
|
...bodyTexts(request.body)
|
|
10493
10747
|
];
|
|
10494
10748
|
return wire.some((text3) => SECRET_PLACEHOLDER.test(text3));
|
|
10495
10749
|
}
|
|
10496
|
-
function
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
return "";
|
|
10502
|
-
}
|
|
10750
|
+
function formDecoded(text3) {
|
|
10751
|
+
return text3.replaceAll("+", " ").replace(
|
|
10752
|
+
/(?:%[0-9a-f]{2})+/gi,
|
|
10753
|
+
(run) => utf82.decode(Uint8Array.from(run.slice(1).split("%"), (hex2) => parseInt(hex2, 16)))
|
|
10754
|
+
);
|
|
10503
10755
|
}
|
|
10756
|
+
var utf82 = new TextDecoder();
|
|
10504
10757
|
function bodyTexts(body3) {
|
|
10505
10758
|
if (typeof body3 === "string") {
|
|
10506
|
-
return [body3,
|
|
10759
|
+
return [body3, formDecoded(body3)];
|
|
10507
10760
|
}
|
|
10508
10761
|
if (body3 instanceof FormData) {
|
|
10509
10762
|
return [...body3].flatMap(([name, value]) => [name, typeof value === "string" ? value : value.name]);
|
|
@@ -10521,6 +10774,32 @@ function decodeBasicCredentials(headerValue) {
|
|
|
10521
10774
|
}
|
|
10522
10775
|
}
|
|
10523
10776
|
|
|
10777
|
+
// ../core/src/scripting/request-view.ts
|
|
10778
|
+
function scriptRequestOf(content) {
|
|
10779
|
+
const rows2 = (list4) => list4.filter((row) => row.enabled && row.key).map((row) => [row.key, row.value]);
|
|
10780
|
+
const { body: body3 } = content;
|
|
10781
|
+
const seen = body3.mode === "raw" ? { mode: "raw", raw: body3.raw?.text ?? "" } : body3.mode === "graphql" ? { mode: "raw", raw: graphqlText(body3.graphql?.query ?? "", body3.graphql?.variables ?? "") } : body3.mode === "urlencoded" ? { mode: "urlencoded", urlencoded: rows2(body3.urlencoded ?? []) } : { mode: body3.mode };
|
|
10782
|
+
return { method: content.method, url: content.url, headers: rows2(content.headers), body: seen };
|
|
10783
|
+
}
|
|
10784
|
+
function withScriptRequest(content, request) {
|
|
10785
|
+
const rows2 = (pairs2) => pairs2.map(([key, value]) => ({ key, value, enabled: true }));
|
|
10786
|
+
const untouched = content.body.mode === "graphql" && request.body.mode === "raw" && request.body.raw === graphqlText(content.body.graphql?.query ?? "", content.body.graphql?.variables ?? "");
|
|
10787
|
+
const body3 = untouched ? content.body : request.body.mode === "raw" ? {
|
|
10788
|
+
...content.body,
|
|
10789
|
+
mode: "raw",
|
|
10790
|
+
raw: {
|
|
10791
|
+
language: content.body.mode === "graphql" ? "json" : content.body.raw?.language ?? "text",
|
|
10792
|
+
text: request.body.raw
|
|
10793
|
+
}
|
|
10794
|
+
} : request.body.mode === "urlencoded" ? { ...content.body, mode: "urlencoded", urlencoded: rows2(request.body.urlencoded) } : content.body;
|
|
10795
|
+
return { ...content, method: request.method, url: request.url, headers: rows2(request.headers), body: body3 };
|
|
10796
|
+
}
|
|
10797
|
+
function sentScriptRequest(content, sent) {
|
|
10798
|
+
const { mode } = content.body;
|
|
10799
|
+
const body3 = mode === "multipart" || mode === "binary" ? { mode } : sent.body === void 0 ? { mode: "none" } : mode === "urlencoded" ? { mode, urlencoded: [...new URLSearchParams(sent.body)] } : { mode: "raw", raw: sent.body };
|
|
10800
|
+
return { method: sent.method, url: sent.url, headers: sent.headers, body: body3 };
|
|
10801
|
+
}
|
|
10802
|
+
|
|
10524
10803
|
// ../core/src/send/assertions.ts
|
|
10525
10804
|
function runAssertions(assertions, response) {
|
|
10526
10805
|
return assertions.map((assertion) => evaluate2(assertion, response));
|
|
@@ -10682,19 +10961,22 @@ var RequestSkipped = class extends SendBlockedError {
|
|
|
10682
10961
|
scriptLogs;
|
|
10683
10962
|
nextRequest;
|
|
10684
10963
|
};
|
|
10685
|
-
var
|
|
10686
|
-
var
|
|
10964
|
+
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.";
|
|
10965
|
+
var OFFLINE_TEXT = "The request got no response: this browser is offline. Connect, then send it again.";
|
|
10966
|
+
var noResponseWithoutPage = (reason) => `The request got no response: ${reason}. Check the address, that the name resolves and that the server accepts connections.`;
|
|
10687
10967
|
var SECRET_PLACEHOLDER_BLOCKED = "This request refers to a vault secret that could not be read (it may have been deleted), so it was not sent. Give the variable a value again.";
|
|
10688
10968
|
function sentBodyPreview(body3) {
|
|
10689
10969
|
if (body3 === void 0 || typeof body3 === "string") return body3;
|
|
10690
10970
|
const file = (blob) => `(${blob.size} bytes${blob.type ? `, ${blob.type}` : ""})`;
|
|
10691
10971
|
if (body3 instanceof FormData) {
|
|
10692
|
-
return [...body3].map(
|
|
10972
|
+
return [...body3].map(
|
|
10973
|
+
([name, value]) => typeof value === "string" ? `${name}=${value}` : `${name}=@${value.name} ${file(value)}`
|
|
10974
|
+
).join("\n");
|
|
10693
10975
|
}
|
|
10694
10976
|
return `@file ${file(body3)}`;
|
|
10695
10977
|
}
|
|
10696
10978
|
function scriptResponse(response) {
|
|
10697
|
-
const body3 = bodyText(response.body);
|
|
10979
|
+
const body3 = response.text ?? bodyText(response.body);
|
|
10698
10980
|
return {
|
|
10699
10981
|
code: response.statusCode,
|
|
10700
10982
|
status: response.statusText,
|
|
@@ -10710,7 +10992,10 @@ function bodyText(body3) {
|
|
|
10710
10992
|
return stringifyJson(body3) ?? "";
|
|
10711
10993
|
}
|
|
10712
10994
|
function lastNext(runs) {
|
|
10713
|
-
return runs.reduce(
|
|
10995
|
+
return runs.reduce(
|
|
10996
|
+
(last, run) => run.result.nextRequest !== void 0 ? run.result.nextRequest : last,
|
|
10997
|
+
void 0
|
|
10998
|
+
);
|
|
10714
10999
|
}
|
|
10715
11000
|
function scriptsToRun(spec, kind) {
|
|
10716
11001
|
const label = kind === "pre" ? "Pre-request script" : "Post-response script";
|
|
@@ -10722,7 +11007,11 @@ function scriptsToRun(spec, kind) {
|
|
|
10722
11007
|
function scriptRows(result, name, redactor) {
|
|
10723
11008
|
if (!result) return [];
|
|
10724
11009
|
const rows2 = result.error === void 0 ? result.testResults : [...result.testResults, { label: name, passed: false, error: result.error, source: "script" }];
|
|
10725
|
-
return rows2.map((row) => ({
|
|
11010
|
+
return rows2.map((row) => ({
|
|
11011
|
+
...row,
|
|
11012
|
+
label: redactor.text(row.label),
|
|
11013
|
+
...row.error !== void 0 && { error: redactor.text(row.error) }
|
|
11014
|
+
}));
|
|
10726
11015
|
}
|
|
10727
11016
|
async function exchange(spec, ports) {
|
|
10728
11017
|
const createdAt = Date.now();
|
|
@@ -10733,7 +11022,11 @@ async function exchange(spec, ports) {
|
|
|
10733
11022
|
let undo = () => Promise.resolve();
|
|
10734
11023
|
const preScripts = spec.runScripts ? scriptsToRun(spec, "pre") : [];
|
|
10735
11024
|
if (preScripts.length) {
|
|
10736
|
-
const before = {
|
|
11025
|
+
const before = {
|
|
11026
|
+
environment: ports.environment()?.vars ?? [],
|
|
11027
|
+
global: ports.globals(),
|
|
11028
|
+
collection: spec.collection?.variables() ?? []
|
|
11029
|
+
};
|
|
10737
11030
|
const touched = emptyChanges();
|
|
10738
11031
|
const restoreSecrets = [];
|
|
10739
11032
|
const was = (rows2, names) => names.map(({ key }) => {
|
|
@@ -10741,29 +11034,54 @@ async function exchange(spec, ports) {
|
|
|
10741
11034
|
return { key, value, ...json && { json } };
|
|
10742
11035
|
});
|
|
10743
11036
|
undo = async () => {
|
|
10744
|
-
await applyChanges(
|
|
11037
|
+
await applyChanges(
|
|
11038
|
+
ports,
|
|
11039
|
+
spec,
|
|
11040
|
+
{
|
|
11041
|
+
environment: was(before.environment, touched.environment),
|
|
11042
|
+
global: was(before.global, touched.global),
|
|
11043
|
+
collection: was(before.collection, touched.collection)
|
|
11044
|
+
},
|
|
11045
|
+
[]
|
|
11046
|
+
);
|
|
10745
11047
|
for (const restore of restoreSecrets.reverse()) await restore();
|
|
10746
11048
|
};
|
|
10747
11049
|
timings.preScriptMs = 0;
|
|
10748
11050
|
for (const { name, text: text3 } of preScripts) {
|
|
10749
11051
|
const scriptStarted = performance.now();
|
|
10750
|
-
const result = await ports.runScript(
|
|
11052
|
+
const result = await ports.runScript(
|
|
11053
|
+
text3,
|
|
11054
|
+
environmentSnapshot(ports),
|
|
11055
|
+
void 0,
|
|
11056
|
+
scriptExtras(ports, spec, "prerequest", scriptRequestOf(template))
|
|
11057
|
+
);
|
|
10751
11058
|
timings.preScriptMs += Math.round(performance.now() - scriptStarted);
|
|
11059
|
+
stopped(spec);
|
|
10752
11060
|
preRuns.push({ name, result });
|
|
10753
11061
|
const { changes } = result;
|
|
10754
11062
|
restoreSecrets.push(await applyChanges(ports, spec, changes, notes));
|
|
10755
11063
|
for (const scope of ["environment", "global", "collection"]) touched[scope].push(...changes[scope]);
|
|
10756
11064
|
if (result.error !== void 0) {
|
|
10757
11065
|
const nothing = new Redactor([]);
|
|
10758
|
-
throw new PreRequestScriptError(
|
|
11066
|
+
throw new PreRequestScriptError(
|
|
11067
|
+
preRuns.flatMap((run) => scriptRows(run.result, run.name, nothing)),
|
|
11068
|
+
[...preRuns.flatMap((run) => run.result.logs), ...notes],
|
|
11069
|
+
timings,
|
|
11070
|
+
name
|
|
11071
|
+
);
|
|
10759
11072
|
}
|
|
10760
11073
|
if (result.request) template = withScriptRequest(template, result.request);
|
|
10761
11074
|
}
|
|
10762
11075
|
if (spec.iteration && preRuns.some((run) => run.result.skipRequest)) {
|
|
10763
11076
|
const nothing = new Redactor([]);
|
|
10764
|
-
throw new RequestSkipped(
|
|
11077
|
+
throw new RequestSkipped(
|
|
11078
|
+
preRuns.flatMap((run) => scriptRows(run.result, run.name, nothing)),
|
|
11079
|
+
[...preRuns.flatMap((run) => run.result.logs), ...notes],
|
|
11080
|
+
lastNext(preRuns)
|
|
11081
|
+
);
|
|
10765
11082
|
}
|
|
10766
11083
|
}
|
|
11084
|
+
stopped(spec);
|
|
10767
11085
|
let request;
|
|
10768
11086
|
try {
|
|
10769
11087
|
const built2 = spec.buildRequest(template);
|
|
@@ -10782,16 +11100,22 @@ async function exchange(spec, ports) {
|
|
|
10782
11100
|
const jar = ports.cookies;
|
|
10783
11101
|
const cookies = jar && {
|
|
10784
11102
|
for: (url) => {
|
|
10785
|
-
const found = jar.for(url).filter(
|
|
11103
|
+
const found = jar.for(url).filter(
|
|
11104
|
+
(cookie) => !containsSecretPlaceholder({ url: "", headers: [["Cookie", `${cookie.name}=${cookie.value}`]] })
|
|
11105
|
+
);
|
|
10786
11106
|
sentCookies ??= found;
|
|
10787
11107
|
for (const cookie of found) cookieValues.add(cookie.value);
|
|
10788
11108
|
return found;
|
|
10789
11109
|
},
|
|
10790
11110
|
store: (url, setCookies) => void jar.store(url, setCookies, request.secrets)
|
|
10791
11111
|
};
|
|
10792
|
-
const policy = {
|
|
11112
|
+
const policy = {
|
|
11113
|
+
route: spec.route ?? ports.transport.route(),
|
|
11114
|
+
...spec.askRoute && { ask: spec.askRoute }
|
|
11115
|
+
};
|
|
10793
11116
|
const transmit = async () => {
|
|
10794
11117
|
startedAt = performance.now();
|
|
11118
|
+
delete policy.startedAt;
|
|
10795
11119
|
sentCookies = void 0;
|
|
10796
11120
|
try {
|
|
10797
11121
|
return await ports.transport.send(
|
|
@@ -10827,10 +11151,10 @@ async function exchange(spec, ports) {
|
|
|
10827
11151
|
outcome = await transmit();
|
|
10828
11152
|
}
|
|
10829
11153
|
}
|
|
10830
|
-
const durationMs = Math.round(performance.now() - startedAt);
|
|
11154
|
+
const durationMs = Math.round(performance.now() - (policy.startedAt ?? startedAt));
|
|
10831
11155
|
timings.requestMs = durationMs;
|
|
10832
11156
|
ports.watch?.received(request.url);
|
|
10833
|
-
const shaped = outcome instanceof TransportError ? shapeFailure(outcome,
|
|
11157
|
+
const shaped = outcome instanceof TransportError ? shapeFailure(outcome, ports.page, ports.online?.()) : shapeResponse(outcome, ports.page);
|
|
10834
11158
|
const redactor = new Redactor([...request.secrets, ...request.credentials, ...cookieValues]);
|
|
10835
11159
|
const secrets = new Redactor(request.secrets);
|
|
10836
11160
|
const post = await runPostScriptAndAssertions(
|
|
@@ -10848,7 +11172,15 @@ async function exchange(spec, ports) {
|
|
|
10848
11172
|
notes,
|
|
10849
11173
|
secrets,
|
|
10850
11174
|
timings,
|
|
10851
|
-
{
|
|
11175
|
+
{
|
|
11176
|
+
statusCode: shaped.response.statusCode ?? 0,
|
|
11177
|
+
statusText: shaped.response.statusText ?? "",
|
|
11178
|
+
body: shaped.body,
|
|
11179
|
+
...shaped.text !== void 0 && { text: shaped.text },
|
|
11180
|
+
headers: shaped.headers,
|
|
11181
|
+
durationMs,
|
|
11182
|
+
sizeBytes: outcome instanceof TransportError ? void 0 : outcome.sizes.decoded
|
|
11183
|
+
}
|
|
10852
11184
|
);
|
|
10853
11185
|
const runs = [...preRuns, ...post.scripts];
|
|
10854
11186
|
const testResults = [...runs.flatMap((run) => scriptRows(run.result, run.name, redactor)), ...post.assertions];
|
|
@@ -10868,7 +11200,10 @@ async function exchange(spec, ports) {
|
|
|
10868
11200
|
...preview !== void 0 && { bodyPreview: keep(preview).text }
|
|
10869
11201
|
},
|
|
10870
11202
|
route: outcome instanceof TransportError ? policy.took ?? ports.transport.first(policy.route) : outcome.route,
|
|
10871
|
-
durationMs
|
|
11203
|
+
durationMs,
|
|
11204
|
+
...spec.info && {
|
|
11205
|
+
sentFrom: { name: redactor.text(spec.info.requestName), path: spec.info.path.map((name) => redactor.text(name)) }
|
|
11206
|
+
}
|
|
10872
11207
|
};
|
|
10873
11208
|
if (!(outcome instanceof TransportError)) {
|
|
10874
11209
|
history.response = {
|
|
@@ -10882,7 +11217,25 @@ async function exchange(spec, ports) {
|
|
|
10882
11217
|
history.error = redactor.text(shaped.historyError);
|
|
10883
11218
|
}
|
|
10884
11219
|
const nextRequest = lastNext(runs);
|
|
11220
|
+
const record = recordOf(history, {
|
|
11221
|
+
sentHeaders: withJarCookies(request.headers, sentCookies ?? []),
|
|
11222
|
+
sentUrl: request.url,
|
|
11223
|
+
sentPreview: preview,
|
|
11224
|
+
received: outcome instanceof TransportError ? void 0 : { headers: shaped.headerList, body: shaped.bodyText },
|
|
11225
|
+
vaulted: (request.vaulted ?? []).map((secret) => ({
|
|
11226
|
+
...secret,
|
|
11227
|
+
...secret.scope && { holder: holderOf(secret.scope, ports, spec) }
|
|
11228
|
+
})),
|
|
11229
|
+
unpaired: outcome instanceof TransportError && history.route === "direct" && policy.unpaired === true,
|
|
11230
|
+
failure: outcome instanceof TransportError ? {
|
|
11231
|
+
kind: outcome.kind,
|
|
11232
|
+
reported: redactor.text(outcome.message),
|
|
11233
|
+
summary: redactor.text(firstSentence(shaped.response.errorText))
|
|
11234
|
+
} : void 0
|
|
11235
|
+
});
|
|
11236
|
+
history.record = record;
|
|
10885
11237
|
return {
|
|
11238
|
+
record,
|
|
10886
11239
|
durationMs,
|
|
10887
11240
|
timings,
|
|
10888
11241
|
...!(outcome instanceof TransportError) && outcome.timings && { relayTimings: outcome.timings },
|
|
@@ -10894,21 +11247,57 @@ async function exchange(spec, ports) {
|
|
|
10894
11247
|
...nextRequest !== void 0 && { nextRequest }
|
|
10895
11248
|
};
|
|
10896
11249
|
}
|
|
10897
|
-
function
|
|
10898
|
-
|
|
11250
|
+
function stopped(spec) {
|
|
11251
|
+
if (spec.signal?.aborted) throw new SendBlockedError("You stopped the request. Nothing was sent.");
|
|
11252
|
+
}
|
|
11253
|
+
function firstSentence(text3) {
|
|
11254
|
+
const match = /^.*?[.!?](?=\s|$)/s.exec(text3.trim());
|
|
11255
|
+
return (match ? match[0] : text3).trim();
|
|
11256
|
+
}
|
|
11257
|
+
function holderOf(scope, ports, spec) {
|
|
11258
|
+
if (scope === "environment") {
|
|
11259
|
+
const name = ports.environment()?.name;
|
|
11260
|
+
return name === void 0 ? void 0 : `environment "${name}"`;
|
|
11261
|
+
}
|
|
11262
|
+
if (scope === "collection") return spec.inherited?.[0]?.name;
|
|
11263
|
+
return void 0;
|
|
11264
|
+
}
|
|
11265
|
+
function recordOf(history, raw) {
|
|
11266
|
+
const changed = (kept, sent) => kept.filter(([, value], index) => value !== sent[index]?.[1]).map(([name]) => name);
|
|
11267
|
+
const response = history.response;
|
|
11268
|
+
const keptUrl = history.sent.url;
|
|
11269
|
+
return {
|
|
11270
|
+
...response && { status: { code: response.status, text: response.statusText } },
|
|
11271
|
+
durationMs: history.durationMs ?? 0,
|
|
11272
|
+
route: history.route,
|
|
11273
|
+
host: URL.parse(keptUrl)?.host || keptUrl,
|
|
11274
|
+
secrets: raw.vaulted,
|
|
11275
|
+
masked: {
|
|
11276
|
+
...keptUrl !== raw.sentUrl && { address: true },
|
|
11277
|
+
headers: [...new Set(changed(history.sent.headers, raw.sentHeaders))],
|
|
11278
|
+
...raw.sentPreview !== void 0 && history.sent.bodyPreview !== raw.sentPreview.slice(0, HISTORY_BODY_LIMIT) && { body: true },
|
|
11279
|
+
responseHeaders: response && raw.received ? [...new Set(changed(response.headers, raw.received.headers))] : [],
|
|
11280
|
+
...response?.body && raw.received?.body !== void 0 && response.body.text !== raw.received.body.slice(0, HISTORY_BODY_LIMIT) && { responseBody: true }
|
|
11281
|
+
},
|
|
11282
|
+
kept: { response: response !== void 0, body: response?.body !== void 0 },
|
|
11283
|
+
...raw.unpaired && { unpaired: true },
|
|
11284
|
+
...raw.failure && { failure: raw.failure }
|
|
11285
|
+
};
|
|
11286
|
+
}
|
|
11287
|
+
function shapeResponse(envelope, page) {
|
|
11288
|
+
const { value: body3, text: sentText } = readEnvelope(envelope);
|
|
10899
11289
|
const isError = envelope.status < 200 || envelope.status >= 300;
|
|
10900
|
-
const
|
|
10901
|
-
const shown = isError ? body3 ?? failureText : body3;
|
|
11290
|
+
const shown = body3;
|
|
10902
11291
|
const bodyIsJson = isJsonPayload(shown);
|
|
10903
11292
|
const text3 = bodyIsJson ? serializeJsonPayload(shown) : stringifyPayload(shown);
|
|
10904
11293
|
const header = (name) => envelope.headers.find(([key]) => key.toLowerCase() === name)?.[1];
|
|
10905
11294
|
const contentLength = Number(header("content-length") || NaN);
|
|
10906
11295
|
return {
|
|
10907
11296
|
body: body3,
|
|
11297
|
+
...sentText !== void 0 && { text: sentText },
|
|
10908
11298
|
headers: Object.fromEntries(envelope.headers),
|
|
10909
11299
|
headerList: envelope.headers,
|
|
10910
11300
|
bodyText: body3 === void 0 || body3 === null || body3 instanceof BinaryBody ? void 0 : text3,
|
|
10911
|
-
historyError: isError ? failureText : void 0,
|
|
10912
11301
|
response: {
|
|
10913
11302
|
isError,
|
|
10914
11303
|
statusCode: envelope.status,
|
|
@@ -10917,34 +11306,36 @@ function shapeResponse(envelope, url, page) {
|
|
|
10917
11306
|
dataText: isError ? "" : text3,
|
|
10918
11307
|
errorText: isError ? text3 : "",
|
|
10919
11308
|
headersView: envelope.headers.map(([name, value]) => ({ name, value })).sort((a, b) => a.name.localeCompare(b.name)),
|
|
10920
|
-
contentLength: Number.isFinite(contentLength) ? contentLength :
|
|
11309
|
+
contentLength: Number.isFinite(contentLength) ? contentLength : envelope.sizes.decoded,
|
|
10921
11310
|
binary: body3 instanceof BinaryBody ? body3 : void 0,
|
|
10922
11311
|
redirectedTo: envelope.redirected ? envelope.finalUrl : void 0,
|
|
10923
11312
|
route: envelope.route,
|
|
10924
11313
|
contentType: header("content-type"),
|
|
10925
|
-
// Outside a page nothing is withheld: no browser stands between.
|
|
10926
|
-
|
|
11314
|
+
// Outside a page nothing is withheld: no browser stands between. A wildcard exposes every header
|
|
11315
|
+
// but Set-Cookie to a request without credentials, which every direct send is (U-173).
|
|
11316
|
+
headersLimited: page !== void 0 && envelope.route === "direct" && URL.parse(envelope.finalUrl, page)?.origin !== URL.parse(page)?.origin && !(header("access-control-expose-headers") ?? "").split(",").some((name) => name.trim() === "*"),
|
|
10927
11317
|
...envelope.stream && { stream: envelope.stream },
|
|
10928
11318
|
...envelope.redirects && { redirects: envelope.redirects },
|
|
10929
11319
|
...envelope.tlsInsecure && { tlsInsecure: true }
|
|
10930
11320
|
}
|
|
10931
11321
|
};
|
|
10932
11322
|
}
|
|
10933
|
-
function shapeFailure(failure,
|
|
11323
|
+
function shapeFailure(failure, page, online) {
|
|
10934
11324
|
const network = failure.kind === "network";
|
|
10935
|
-
const
|
|
11325
|
+
const said2 = network || failure.kind === "bridge";
|
|
11326
|
+
const errorText = !network ? failure.message : page === void 0 ? noResponseWithoutPage(failure.message) : online === false ? OFFLINE_TEXT : NO_RESPONSE_TEXT;
|
|
10936
11327
|
return {
|
|
10937
|
-
body:
|
|
11328
|
+
body: said2 ? void 0 : failure.message,
|
|
10938
11329
|
headers: {},
|
|
10939
11330
|
headerList: [],
|
|
10940
|
-
historyError: network ? `
|
|
11331
|
+
historyError: network ? page === void 0 ? `No response: ${failure.message}.` : online === false ? "No response: this browser is offline." : "No response. The browser does not say why." : failure.message,
|
|
10941
11332
|
response: {
|
|
10942
11333
|
isError: true,
|
|
10943
|
-
statusCode,
|
|
10944
|
-
statusText:
|
|
11334
|
+
statusCode: void 0,
|
|
11335
|
+
statusText: "",
|
|
10945
11336
|
bodyIsJson: false,
|
|
10946
11337
|
dataText: "",
|
|
10947
|
-
errorText
|
|
11338
|
+
errorText,
|
|
10948
11339
|
headersView: []
|
|
10949
11340
|
}
|
|
10950
11341
|
};
|
|
@@ -10953,13 +11344,21 @@ async function runPostScriptAndAssertions(ports, spec, sent, notes, variables2,
|
|
|
10953
11344
|
const scripts2 = [];
|
|
10954
11345
|
for (const { name, text: text3 } of spec.runScripts ? scriptsToRun(spec, "post") : []) {
|
|
10955
11346
|
const scriptStarted = performance.now();
|
|
10956
|
-
const result = await ports.runScript(
|
|
11347
|
+
const result = await ports.runScript(
|
|
11348
|
+
text3,
|
|
11349
|
+
environmentSnapshot(ports),
|
|
11350
|
+
response,
|
|
11351
|
+
scriptExtras(ports, spec, "test", sent)
|
|
11352
|
+
);
|
|
10957
11353
|
timings.postScriptMs = (timings.postScriptMs ?? 0) + Math.round(performance.now() - scriptStarted);
|
|
10958
11354
|
scripts2.push({ name, result });
|
|
10959
11355
|
await applyChanges(ports, spec, result.changes, notes, variables2);
|
|
10960
11356
|
}
|
|
10961
11357
|
const { statusCode, body: body3, headers: headers2, durationMs } = response;
|
|
10962
|
-
return {
|
|
11358
|
+
return {
|
|
11359
|
+
scripts: scripts2,
|
|
11360
|
+
assertions: spec.tests.length ? runAssertions(spec.tests, { statusCode, body: body3, headers: headers2, durationMs }) : []
|
|
11361
|
+
};
|
|
10963
11362
|
}
|
|
10964
11363
|
function environmentSnapshot(ports) {
|
|
10965
11364
|
return scriptVariables(ports.environment()?.vars ?? []);
|
|
@@ -10972,30 +11371,56 @@ function scriptExtras(ports, spec, eventName, request) {
|
|
|
10972
11371
|
...spec.iteration && { data: scriptVariables(spec.iteration.data) },
|
|
10973
11372
|
collection: scriptVariables(spec.collection?.variables() ?? []),
|
|
10974
11373
|
request,
|
|
10975
|
-
info: {
|
|
11374
|
+
info: {
|
|
11375
|
+
eventName,
|
|
11376
|
+
requestName: spec.info?.requestName ?? "",
|
|
11377
|
+
requestId: spec.info?.requestId ?? "",
|
|
11378
|
+
...spec.iteration && { iteration: spec.iteration.index, iterationCount: spec.iteration.count }
|
|
11379
|
+
},
|
|
10976
11380
|
send: (request2) => sendForScript(ports, request2, spec.signal, spec.route)
|
|
10977
11381
|
};
|
|
10978
11382
|
}
|
|
10979
11383
|
async function sendForScript(ports, request, signal, route = ports.transport.route()) {
|
|
10980
11384
|
if (containsSecretPlaceholder(request)) {
|
|
10981
|
-
throw Object.assign(
|
|
11385
|
+
throw Object.assign(
|
|
11386
|
+
new Error(
|
|
11387
|
+
"pm.sendRequest with a vault secret is not supported \u2014 see docs/postman-compatibility.md#pm-sendrequest"
|
|
11388
|
+
),
|
|
11389
|
+
{ name: "WayfarerUnsupportedError" }
|
|
11390
|
+
);
|
|
10982
11391
|
}
|
|
10983
11392
|
const startedAt = performance.now();
|
|
10984
|
-
const
|
|
10985
|
-
const
|
|
11393
|
+
const policy = { route };
|
|
11394
|
+
const envelope = await ports.transport.send(
|
|
11395
|
+
request,
|
|
11396
|
+
{
|
|
11397
|
+
signal: signal ?? new AbortController().signal,
|
|
11398
|
+
timeoutMs: ports.timeoutMs(),
|
|
11399
|
+
// Followed on every route, as a browser follows it: the vault is not read for this request, so no secret is held to its host.
|
|
11400
|
+
redirects: { follow: true, stayOnHost: false }
|
|
11401
|
+
},
|
|
11402
|
+
policy
|
|
11403
|
+
);
|
|
11404
|
+
const shaped = shapeResponse(envelope, ports.page);
|
|
10986
11405
|
return scriptResponse({
|
|
10987
11406
|
statusCode: envelope.status,
|
|
10988
11407
|
statusText: envelope.statusText,
|
|
10989
11408
|
body: shaped.body,
|
|
11409
|
+
...shaped.text !== void 0 && { text: shaped.text },
|
|
10990
11410
|
headers: shaped.headers,
|
|
10991
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
11411
|
+
durationMs: Math.round(performance.now() - (policy.startedAt ?? startedAt)),
|
|
10992
11412
|
sizeBytes: envelope.sizes.decoded
|
|
10993
11413
|
});
|
|
10994
11414
|
}
|
|
10995
11415
|
async function applyChanges(ports, spec, changes, notes, secrets) {
|
|
10996
11416
|
const active = ports.environment();
|
|
10997
11417
|
const replaced = /* @__PURE__ */ new Map();
|
|
10998
|
-
const kept = async (api, rows2, scope) => (await intoVault(ports, api, rows2, scope, notes, replaced)).map(({ key, value, json }) => ({
|
|
11418
|
+
const kept = async (api, rows2, scope) => (await intoVault(ports, api, rows2, scope, notes, replaced)).map(({ key, value, json }) => ({
|
|
11419
|
+
key,
|
|
11420
|
+
value: value === null || !secrets ? value : secrets.text(value),
|
|
11421
|
+
keepSecret: true,
|
|
11422
|
+
...json && { json }
|
|
11423
|
+
}));
|
|
10999
11424
|
const environment2 = await kept("pm.environment", active?.vars ?? [], changes.environment);
|
|
11000
11425
|
const global = await kept("pm.globals", ports.globals(), changes.global);
|
|
11001
11426
|
const collection = await kept("pm.collectionVariables", spec.collection?.variables() ?? [], changes.collection);
|
|
@@ -11006,7 +11431,10 @@ async function applyChanges(ports, spec, changes, notes, secrets) {
|
|
|
11006
11431
|
if (global.length) await ports.changeGlobals(global);
|
|
11007
11432
|
if (collection.length) {
|
|
11008
11433
|
if (spec.collection) await spec.collection.change(collection);
|
|
11009
|
-
else
|
|
11434
|
+
else
|
|
11435
|
+
notes.push(
|
|
11436
|
+
"[warn] pm.collectionVariables: this request is in no collection, so what the script set was not kept."
|
|
11437
|
+
);
|
|
11010
11438
|
}
|
|
11011
11439
|
return async () => {
|
|
11012
11440
|
for (const [id, plaintext] of replaced) await ports.vault.replaceSecret(id, plaintext);
|
|
@@ -11026,7 +11454,8 @@ async function intoVault(ports, api, rows2, changes, notes, replaced) {
|
|
|
11026
11454
|
continue;
|
|
11027
11455
|
}
|
|
11028
11456
|
const old = await ports.vault.readSecret(id);
|
|
11029
|
-
if (old === null || !await ports.vault.replaceSecret(id, change.value))
|
|
11457
|
+
if (old === null || !await ports.vault.replaceSecret(id, change.value))
|
|
11458
|
+
refused("the secret this variable refers to is not in the vault");
|
|
11030
11459
|
else if (!replaced.has(id)) replaced.set(id, old);
|
|
11031
11460
|
}
|
|
11032
11461
|
}
|
|
@@ -11063,13 +11492,97 @@ function stringifyPayload(payload) {
|
|
|
11063
11492
|
if (typeof payload === "string") {
|
|
11064
11493
|
return payload;
|
|
11065
11494
|
}
|
|
11066
|
-
return stringifyJson(payload, 4) ?? String(payload);
|
|
11495
|
+
return stringifyJson(payload, 4) ?? String(payload);
|
|
11496
|
+
}
|
|
11497
|
+
|
|
11498
|
+
// ../core/src/auth/sigv4.ts
|
|
11499
|
+
var utf83 = (text3) => new TextEncoder().encode(text3);
|
|
11500
|
+
var sha2562 = async (data) => new Uint8Array(await crypto.subtle.digest("SHA-256", data));
|
|
11501
|
+
var hmac2 = async (key, data) => new Uint8Array(
|
|
11502
|
+
await crypto.subtle.sign(
|
|
11503
|
+
"HMAC",
|
|
11504
|
+
await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]),
|
|
11505
|
+
utf83(data)
|
|
11506
|
+
)
|
|
11507
|
+
);
|
|
11508
|
+
var hex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
11509
|
+
var encode = (text3) => encodeURIComponent(text3).replace(/[!'()*]/g, (mark) => `%${mark.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
11510
|
+
var decode = (text3) => {
|
|
11511
|
+
try {
|
|
11512
|
+
return decodeURIComponent(text3);
|
|
11513
|
+
} catch (error) {
|
|
11514
|
+
if (!(error instanceof URIError)) throw error;
|
|
11515
|
+
return text3;
|
|
11516
|
+
}
|
|
11517
|
+
};
|
|
11518
|
+
async function signV4(request, credentials, at) {
|
|
11519
|
+
const url = new URL(request.url);
|
|
11520
|
+
const s3 = credentials.service === "s3";
|
|
11521
|
+
const segments = url.pathname.split("/");
|
|
11522
|
+
const kept = segments.filter(Boolean);
|
|
11523
|
+
const path = s3 ? segments.map((segment) => encode(decode(segment))).join("/") : `/${kept.map(encode).join("/")}${kept.length && url.pathname.endsWith("/") ? "/" : ""}`;
|
|
11524
|
+
const query = url.search.slice(1).split("&").filter(Boolean).map((pair) => {
|
|
11525
|
+
const cut = pair.indexOf("=");
|
|
11526
|
+
return [
|
|
11527
|
+
encode(decode(cut === -1 ? pair : pair.slice(0, cut))),
|
|
11528
|
+
encode(decode(cut === -1 ? "" : pair.slice(cut + 1)))
|
|
11529
|
+
];
|
|
11530
|
+
}).sort(
|
|
11531
|
+
([keyA, valueA], [keyB, valueB]) => keyA === keyB ? valueA < valueB ? -1 : valueA > valueB ? 1 : 0 : keyA < keyB ? -1 : 1
|
|
11532
|
+
).map(([key2, value]) => `${key2}=${value}`).join("&");
|
|
11533
|
+
const date = at.toISOString().replace(/[-:]|\.\d{3}/g, "");
|
|
11534
|
+
const day = date.slice(0, 8);
|
|
11535
|
+
const body3 = typeof request.body === "string" ? utf83(request.body) : new Uint8Array(request.body ?? []);
|
|
11536
|
+
const bodyHash = hex(await sha2562(body3));
|
|
11537
|
+
const added2 = [
|
|
11538
|
+
["X-Amz-Date", date],
|
|
11539
|
+
...credentials.sessionToken ? [["X-Amz-Security-Token", credentials.sessionToken]] : [],
|
|
11540
|
+
...s3 ? [["X-Amz-Content-Sha256", bodyHash]] : []
|
|
11541
|
+
];
|
|
11542
|
+
const byName = /* @__PURE__ */ new Map();
|
|
11543
|
+
const own3 = request.headers.filter(
|
|
11544
|
+
([name]) => !added2.some(([written]) => written.toLowerCase() === name.toLowerCase()) && name.toLowerCase() !== "host"
|
|
11545
|
+
);
|
|
11546
|
+
for (const [name, value] of [["Host", url.host], ...own3, ...added2]) {
|
|
11547
|
+
const key2 = name.toLowerCase();
|
|
11548
|
+
byName.set(key2, [...byName.get(key2) ?? [], value.trim().replace(/\s+/g, " ")]);
|
|
11549
|
+
}
|
|
11550
|
+
const names = [...byName.keys()].sort();
|
|
11551
|
+
const signedHeaders = names.join(";");
|
|
11552
|
+
const canonicalRequest = [
|
|
11553
|
+
request.method,
|
|
11554
|
+
path,
|
|
11555
|
+
query,
|
|
11556
|
+
...names.map((name) => `${name}:${(byName.get(name) ?? []).join(",")}`),
|
|
11557
|
+
"",
|
|
11558
|
+
signedHeaders,
|
|
11559
|
+
bodyHash
|
|
11560
|
+
].join("\n");
|
|
11561
|
+
const scope = `${day}/${credentials.region}/${credentials.service}/aws4_request`;
|
|
11562
|
+
const stringToSign = ["AWS4-HMAC-SHA256", date, scope, hex(await sha2562(utf83(canonicalRequest)))].join("\n");
|
|
11563
|
+
let key = utf83(`AWS4${credentials.secretKey}`);
|
|
11564
|
+
for (const part of [day, credentials.region, credentials.service, "aws4_request"]) key = await hmac2(key, part);
|
|
11565
|
+
const signature = hex(await hmac2(key, stringToSign));
|
|
11566
|
+
return {
|
|
11567
|
+
headers: [
|
|
11568
|
+
...added2,
|
|
11569
|
+
[
|
|
11570
|
+
"Authorization",
|
|
11571
|
+
`AWS4-HMAC-SHA256 Credential=${credentials.accessKey}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`
|
|
11572
|
+
]
|
|
11573
|
+
],
|
|
11574
|
+
canonicalRequest,
|
|
11575
|
+
stringToSign,
|
|
11576
|
+
signature
|
|
11577
|
+
};
|
|
11067
11578
|
}
|
|
11068
11579
|
|
|
11069
11580
|
// ../core/src/send/build.ts
|
|
11070
11581
|
var UnresolvedVariablesError = class extends SendBlockedError {
|
|
11071
11582
|
constructor(names) {
|
|
11072
|
-
super(
|
|
11583
|
+
super(
|
|
11584
|
+
`${names.map((name) => `{{${name}}}`).join(", ")} ${names.length === 1 ? "has" : "have"} no value. The request was not sent.`
|
|
11585
|
+
);
|
|
11073
11586
|
this.names = names;
|
|
11074
11587
|
}
|
|
11075
11588
|
names;
|
|
@@ -11087,6 +11600,14 @@ function normalizeUrl(text3) {
|
|
|
11087
11600
|
const address2 = text3.replace(/[\t\n\r]/g, encodeURIComponent);
|
|
11088
11601
|
return hasExplicitScheme(address2) ? address2 : `https://${address2}`;
|
|
11089
11602
|
}
|
|
11603
|
+
function validateUrl(text3) {
|
|
11604
|
+
if (!text3) return false;
|
|
11605
|
+
const parsed2 = URL.parse(normalizeUrl(text3));
|
|
11606
|
+
if (!parsed2 || parsed2.protocol !== "http:" && parsed2.protocol !== "https:" || !parsed2.hostname) return false;
|
|
11607
|
+
if (hasExplicitScheme(text3)) return true;
|
|
11608
|
+
const { hostname } = parsed2;
|
|
11609
|
+
return hostname === "localhost" || hostname.includes(".") || hostname.includes(":") || !!parsed2.port;
|
|
11610
|
+
}
|
|
11090
11611
|
function appendQueryParam(url, key, value) {
|
|
11091
11612
|
const parsed2 = URL.parse(normalizeUrl(url));
|
|
11092
11613
|
if (!parsed2) {
|
|
@@ -11141,17 +11662,24 @@ function buildRequest(content, context) {
|
|
|
11141
11662
|
if (resolver.unresolved.size && context.blockUnresolved) {
|
|
11142
11663
|
throw new UnresolvedVariablesError([...resolver.unresolved]);
|
|
11143
11664
|
}
|
|
11665
|
+
if (!validateUrl(resolver.resolve(content.url.trim()))) {
|
|
11666
|
+
throw new SendBlockedError(
|
|
11667
|
+
"This is not an address a request can go to. Write it as https://host/path. Nothing was sent."
|
|
11668
|
+
);
|
|
11669
|
+
}
|
|
11144
11670
|
return request;
|
|
11145
11671
|
};
|
|
11146
11672
|
return built2 instanceof Promise ? built2.then(check) : check(built2);
|
|
11147
11673
|
};
|
|
11148
|
-
|
|
11149
|
-
|
|
11674
|
+
const oauth = sentAuth(content.auth, context.ancestors).type === "oauth2";
|
|
11675
|
+
if (!probe.lockedSecrets.size && !oauth) return checked(new VariableResolver(scopes()));
|
|
11676
|
+
const withSecrets = () => probe.lockedSecrets.size ? context.readSecrets([...probe.lockedSecrets]).then((secrets) => checked(new VariableResolver(scopes(), (id) => secrets.get(id)))) : checked(new VariableResolver(scopes()));
|
|
11150
11677
|
const from = composed && hostOf(composed.url, composed.scopes);
|
|
11151
11678
|
const to = hostOf(endpointText, scopes());
|
|
11152
11679
|
if (from === void 0 || from === to || composed?.accepted?.has(to)) return withSecrets();
|
|
11153
11680
|
return (context.askHost?.(from, to) ?? Promise.resolve(false)).then((accepted) => {
|
|
11154
|
-
if (!accepted)
|
|
11681
|
+
if (!accepted)
|
|
11682
|
+
throw new SendDeclinedError("The request was not sent, and what the pre-request script set was undone.");
|
|
11155
11683
|
composed?.accepted?.add(to);
|
|
11156
11684
|
return withSecrets();
|
|
11157
11685
|
});
|
|
@@ -11161,11 +11689,17 @@ function build(content, resolver, context) {
|
|
|
11161
11689
|
const auth3 = resolveAuth(sentAuth(content.auth, context.ancestors), resolve);
|
|
11162
11690
|
const withoutValue = [...resolver.unresolved];
|
|
11163
11691
|
const sent = isBodyMethod(content.method) ? resolveBody(content.body, resolve, context) : void 0;
|
|
11164
|
-
const finish = (body3, token2) => sign(
|
|
11692
|
+
const finish = (body3, token2) => sign(
|
|
11693
|
+
assemble(content, body3, resolver, { ancestors: context.ancestors, token: token2, lastEventId: context.lastEventId }),
|
|
11694
|
+
auth3
|
|
11695
|
+
);
|
|
11165
11696
|
const oauth = auth3.type === "oauth2" ? oauthOf(content.auth, context.ancestors, context.scopes()) : null;
|
|
11166
|
-
if (auth3.type !== "oauth2" || !oauth)
|
|
11697
|
+
if (auth3.type !== "oauth2" || !oauth)
|
|
11698
|
+
return sent instanceof Promise ? sent.then((body3) => finish(body3)) : finish(sent);
|
|
11167
11699
|
if (withoutValue.length) {
|
|
11168
|
-
throw new SendBlockedError(
|
|
11700
|
+
throw new SendBlockedError(
|
|
11701
|
+
`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.`
|
|
11702
|
+
);
|
|
11169
11703
|
}
|
|
11170
11704
|
if (!context.oauthToken) throw new SendBlockedError("OAuth 2.0 tokens are not fetched here. Nothing was sent.");
|
|
11171
11705
|
const token = context.oauthToken(auth3, oauth.key, context.renewToken ?? false).catch((error) => {
|
|
@@ -11176,18 +11710,30 @@ function build(content, resolver, context) {
|
|
|
11176
11710
|
function sign(built2, auth3) {
|
|
11177
11711
|
if (auth3.type !== "awsv4" || !URL.canParse(built2.url)) return built2;
|
|
11178
11712
|
if (built2.body instanceof FormData) {
|
|
11179
|
-
throw new SendBlockedError(
|
|
11713
|
+
throw new SendBlockedError(
|
|
11714
|
+
"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."
|
|
11715
|
+
);
|
|
11180
11716
|
}
|
|
11181
|
-
if (!globalThis.crypto?.subtle)
|
|
11717
|
+
if (!globalThis.crypto?.subtle)
|
|
11718
|
+
throw new SendBlockedError(
|
|
11719
|
+
"AWS Signature needs the browser's cryptography, which a page has only over https or from localhost. Nothing was sent."
|
|
11720
|
+
);
|
|
11182
11721
|
const add = async (body3) => {
|
|
11183
11722
|
const own3 = built2.headers.filter(([name]) => /^(content-type$|x-amz-)/i.test(name));
|
|
11184
|
-
const { headers: signature } = await signV4(
|
|
11723
|
+
const { headers: signature } = await signV4(
|
|
11724
|
+
{ method: built2.method, url: built2.url, headers: own3, body: body3 },
|
|
11725
|
+
auth3,
|
|
11726
|
+
/* @__PURE__ */ new Date()
|
|
11727
|
+
);
|
|
11185
11728
|
const replaced = new Set(signature.map(([name]) => name.toLowerCase()));
|
|
11186
11729
|
return {
|
|
11187
11730
|
...built2,
|
|
11188
11731
|
headers: [...built2.headers.filter(([name]) => !replaced.has(name.toLowerCase())), ...signature],
|
|
11189
11732
|
// The signature's header too: a server may send it back.
|
|
11190
|
-
credentials: [
|
|
11733
|
+
credentials: [
|
|
11734
|
+
...built2.credentials,
|
|
11735
|
+
...signature.filter(([name]) => isCredentialHeader(name)).map(([, value]) => value)
|
|
11736
|
+
]
|
|
11191
11737
|
};
|
|
11192
11738
|
};
|
|
11193
11739
|
if (built2.body instanceof Blob) return built2.body.arrayBuffer().then((bytes) => add(new Uint8Array(bytes)));
|
|
@@ -11219,8 +11765,16 @@ function assemble(content, sent, resolver, extra) {
|
|
|
11219
11765
|
body: sent?.data,
|
|
11220
11766
|
// An OAuth 2.0 token is kept like a vault secret and masked like one: in history, in every export, and in what a script is shown.
|
|
11221
11767
|
secrets: [...resolver.taint, ...extra.token ? [extra.token] : []],
|
|
11768
|
+
// By name only, for the exchange record (D4.12): the variables that brought a vault secret in, and the token.
|
|
11769
|
+
vaulted: [
|
|
11770
|
+
...[...resolver.vaulted].map(([name, scope]) => ({ name, ...scope && { scope } })),
|
|
11771
|
+
...extra.token ? [{ name: "OAuth 2.0 token" }] : []
|
|
11772
|
+
],
|
|
11222
11773
|
// Also what a credential header carries, typed by hand or built from the Auth tab: a server may send it back.
|
|
11223
|
-
credentials: [
|
|
11774
|
+
credentials: [
|
|
11775
|
+
...credentialsOf(auth3),
|
|
11776
|
+
...headers2.filter(([name]) => isCredentialHeader(name)).map(([, value]) => value)
|
|
11777
|
+
].filter((value) => value)
|
|
11224
11778
|
};
|
|
11225
11779
|
}
|
|
11226
11780
|
function resolveBody(body3, resolve, context) {
|
|
@@ -11241,7 +11795,11 @@ function resolveBody(body3, resolve, context) {
|
|
|
11241
11795
|
return { data: new URLSearchParams(pairs2).toString(), contentType: "application/x-www-form-urlencoded" };
|
|
11242
11796
|
}
|
|
11243
11797
|
case "multipart":
|
|
11244
|
-
return multipartForm(
|
|
11798
|
+
return multipartForm(
|
|
11799
|
+
(body3.multipart ?? []).filter((item) => item.enabled),
|
|
11800
|
+
resolve,
|
|
11801
|
+
context
|
|
11802
|
+
);
|
|
11245
11803
|
case "binary":
|
|
11246
11804
|
return binaryFile(body3.binary, context);
|
|
11247
11805
|
}
|
|
@@ -11284,10 +11842,19 @@ async function runOutcome(send, declined) {
|
|
|
11284
11842
|
exchange: result.history
|
|
11285
11843
|
};
|
|
11286
11844
|
} catch (error) {
|
|
11287
|
-
if (error instanceof RequestSkipped)
|
|
11845
|
+
if (error instanceof RequestSkipped)
|
|
11846
|
+
return {
|
|
11847
|
+
outcome: {
|
|
11848
|
+
skipped: true,
|
|
11849
|
+
tests: tests(error.testResults),
|
|
11850
|
+
...error.nextRequest !== void 0 && { nextRequest: error.nextRequest }
|
|
11851
|
+
}
|
|
11852
|
+
};
|
|
11288
11853
|
if (error instanceof SendDeclinedError) return { outcome: { error: error.message, tests: [], end: declined } };
|
|
11289
|
-
if (error instanceof PreRequestScriptError)
|
|
11290
|
-
|
|
11854
|
+
if (error instanceof PreRequestScriptError)
|
|
11855
|
+
return { outcome: { error: error.message, tests: tests(error.testResults) } };
|
|
11856
|
+
if (error instanceof SendBlockedError || error instanceof VariableNestingError)
|
|
11857
|
+
return { outcome: { error: error.message, tests: [] } };
|
|
11291
11858
|
throw error;
|
|
11292
11859
|
}
|
|
11293
11860
|
}
|
|
@@ -11314,7 +11881,14 @@ function validateWorkspaceFile(value) {
|
|
|
11314
11881
|
}
|
|
11315
11882
|
return records;
|
|
11316
11883
|
};
|
|
11317
|
-
const [collections, folders, requests, environments, secrets, meta] = [
|
|
11884
|
+
const [collections, folders, requests, environments, secrets, meta] = [
|
|
11885
|
+
"collections",
|
|
11886
|
+
"folders",
|
|
11887
|
+
"requests",
|
|
11888
|
+
"environments",
|
|
11889
|
+
"secrets",
|
|
11890
|
+
"meta"
|
|
11891
|
+
].map(list4);
|
|
11318
11892
|
if (errors.length) return errors;
|
|
11319
11893
|
const idOf2 = (doc) => isObject2(doc["meta"]) ? doc["meta"]["id"] : void 0;
|
|
11320
11894
|
const known = new Set(collections.map(idOf2));
|
|
@@ -11327,12 +11901,21 @@ function validateWorkspaceFile(value) {
|
|
|
11327
11901
|
folders: folders.filter((folder) => folder["collectionId"] === id),
|
|
11328
11902
|
requests: requests.filter((request) => request["collectionId"] === id)
|
|
11329
11903
|
});
|
|
11330
|
-
errors.push(
|
|
11331
|
-
|
|
11904
|
+
errors.push(
|
|
11905
|
+
...(result.errors ?? []).map(
|
|
11906
|
+
(issue) => `collection "${String(collection["name"])}": ${issue.path}: ${issue.message}`
|
|
11907
|
+
)
|
|
11908
|
+
);
|
|
11909
|
+
if (!isObject2(collection["scriptTrust"]))
|
|
11910
|
+
errors.push(`collection "${String(collection["name"])}": scriptTrust is missing.`);
|
|
11332
11911
|
}
|
|
11333
|
-
for (const [kind, docs] of [
|
|
11912
|
+
for (const [kind, docs] of [
|
|
11913
|
+
["folders", folders],
|
|
11914
|
+
["requests", requests]
|
|
11915
|
+
]) {
|
|
11334
11916
|
docs.forEach((doc, index) => {
|
|
11335
|
-
if (!known.has(doc["collectionId"]))
|
|
11917
|
+
if (!known.has(doc["collectionId"]))
|
|
11918
|
+
errors.push(`stores.${kind}[${index}]: belongs to a collection that is not in the file.`);
|
|
11336
11919
|
});
|
|
11337
11920
|
}
|
|
11338
11921
|
errors.push(...validateEnvironmentExport({ $id: ENVIRONMENTS_FORMAT, environments }).errors ?? []);
|
|
@@ -11341,9 +11924,14 @@ function validateWorkspaceFile(value) {
|
|
|
11341
11924
|
const key = record["key"];
|
|
11342
11925
|
if (key === META_STATE_KEY) {
|
|
11343
11926
|
const active = record["activeEnvironmentId"];
|
|
11344
|
-
if (active !== null && active !== void 0 && typeof active !== "string")
|
|
11927
|
+
if (active !== null && active !== void 0 && typeof active !== "string")
|
|
11928
|
+
errors.push("stores.meta: the active environment must be an id or null.");
|
|
11345
11929
|
} else if (key === META_GLOBALS_KEY) {
|
|
11346
|
-
errors.push(
|
|
11930
|
+
errors.push(
|
|
11931
|
+
...validateRows(record["variables"], "stores.meta.globals.variables").map(
|
|
11932
|
+
(issue) => `${issue.path}: ${issue.message}`
|
|
11933
|
+
)
|
|
11934
|
+
);
|
|
11347
11935
|
} else if (key === META_VAULT_KEY) {
|
|
11348
11936
|
vault = record;
|
|
11349
11937
|
} else {
|
|
@@ -11354,7 +11942,8 @@ function validateWorkspaceFile(value) {
|
|
|
11354
11942
|
const issues = validateVaultFile({ $id: VAULT_FILE_FORMAT, vault, secrets });
|
|
11355
11943
|
errors.push(...issues.map((issue) => `vault: ${issue.path}: ${issue.message}`));
|
|
11356
11944
|
secrets.forEach((secret, index) => {
|
|
11357
|
-
if (!isObject2(secret["meta"]) || secret["meta"]["id"] !== secret["id"])
|
|
11945
|
+
if (!isObject2(secret["meta"]) || secret["meta"]["id"] !== secret["id"])
|
|
11946
|
+
errors.push(`stores.secrets[${index}]: meta.id must be the secret's id.`);
|
|
11358
11947
|
});
|
|
11359
11948
|
}
|
|
11360
11949
|
const history = own(stores, "history");
|
|
@@ -11363,16 +11952,47 @@ function validateWorkspaceFile(value) {
|
|
|
11363
11952
|
const path = `stores.history[${index}]`;
|
|
11364
11953
|
const sent = entry["sent"];
|
|
11365
11954
|
if (typeof entry["createdAt"] !== "number") errors.push(`${path}.createdAt: Value must be a number.`);
|
|
11366
|
-
if (entry["route"] !== "direct" && entry["route"] !== "bridge")
|
|
11955
|
+
if (entry["route"] !== "direct" && entry["route"] !== "bridge")
|
|
11956
|
+
errors.push(`${path}.route: Value must be direct or bridge.`);
|
|
11367
11957
|
if (!isObject2(sent) || typeof sent["method"] !== "string" || typeof sent["url"] !== "string" || !Array.isArray(sent["headers"])) {
|
|
11368
11958
|
errors.push(`${path}.sent: Value must hold a method, a URL and headers.`);
|
|
11369
11959
|
}
|
|
11370
|
-
errors.push(
|
|
11960
|
+
errors.push(
|
|
11961
|
+
...validateRequestContent(entry["template"], `${path}.template`).map(
|
|
11962
|
+
(issue) => `${issue.path}: ${issue.message}`
|
|
11963
|
+
)
|
|
11964
|
+
);
|
|
11965
|
+
if (Object.hasOwn(entry, "record")) errors.push(...recordProblems(entry["record"], `${path}.record`));
|
|
11966
|
+
if (Object.hasOwn(entry, "model")) {
|
|
11967
|
+
const model = entry["model"];
|
|
11968
|
+
if (!isObject2(model) || !["tests", "draft", "explain", "test"].includes(String(model["action"])) || typeof model["model"] !== "string" || Object.keys(model).some((key) => key !== "action" && key !== "model"))
|
|
11969
|
+
errors.push(`${path}.model: Value must be the action and the model's name.`);
|
|
11970
|
+
}
|
|
11971
|
+
if (Object.hasOwn(entry, "sentFrom")) {
|
|
11972
|
+
const from = entry["sentFrom"];
|
|
11973
|
+
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"))
|
|
11974
|
+
errors.push(
|
|
11975
|
+
`${path}.sentFrom: Value must be the request's name and the names of its collection and folders.`
|
|
11976
|
+
);
|
|
11977
|
+
}
|
|
11371
11978
|
});
|
|
11372
11979
|
}
|
|
11373
11980
|
return errors;
|
|
11374
11981
|
}
|
|
11375
11982
|
|
|
11983
|
+
// ../core/src/import/import-error.ts
|
|
11984
|
+
var ImportError = class extends Error {
|
|
11985
|
+
constructor(message, issues = [], cause) {
|
|
11986
|
+
super(message, { cause });
|
|
11987
|
+
this.issues = issues;
|
|
11988
|
+
}
|
|
11989
|
+
issues;
|
|
11990
|
+
name = "ImportError";
|
|
11991
|
+
};
|
|
11992
|
+
|
|
11993
|
+
// ../core/src/share/link.ts
|
|
11994
|
+
var SHARE_LINK_MAX_BYTES = 12 * 1024;
|
|
11995
|
+
|
|
11376
11996
|
// ../core/src/run/data-file.ts
|
|
11377
11997
|
var DataFileError = class extends Error {
|
|
11378
11998
|
name = "DataFileError";
|
|
@@ -11414,28 +12034,37 @@ function parseDataFile(text3, fileName) {
|
|
|
11414
12034
|
if (/\.json$/i.test(fileName)) {
|
|
11415
12035
|
const parsed2 = parseJson(text3);
|
|
11416
12036
|
const isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11417
|
-
if (!parsed2.ok || !Array.isArray(parsed2.value) || !parsed2.value.every(isObject5))
|
|
12037
|
+
if (!parsed2.ok || !Array.isArray(parsed2.value) || !parsed2.value.every(isObject5))
|
|
12038
|
+
throw new DataFileError("A JSON data file is a list of objects, one for each iteration.");
|
|
11418
12039
|
return parsed2.value.map(
|
|
11419
|
-
(entry) => Object.entries(entry).map(
|
|
12040
|
+
(entry) => Object.entries(entry).map(
|
|
12041
|
+
([key, value]) => typeof value === "string" ? { key, value, enabled: true } : { key, value: stringifyJson(value) ?? "null", enabled: true, json: true }
|
|
12042
|
+
)
|
|
11420
12043
|
);
|
|
11421
12044
|
}
|
|
11422
12045
|
const [names, ...records] = parseCsv(text3);
|
|
11423
|
-
if (!names?.some((name) => name))
|
|
11424
|
-
|
|
12046
|
+
if (!names?.some((name) => name))
|
|
12047
|
+
throw new DataFileError("A CSV data file has the names of its values in its first line.");
|
|
12048
|
+
return records.filter((record) => record.length > 1 || record[0] !== "").map(
|
|
12049
|
+
(record) => names.flatMap((key, index) => key ? [{ key, value: record[index] ?? "", enabled: true }] : [])
|
|
12050
|
+
);
|
|
11425
12051
|
}
|
|
11426
12052
|
|
|
11427
|
-
// ../core/src/import/import-error.ts
|
|
11428
|
-
var ImportError = class extends Error {
|
|
11429
|
-
constructor(message, issues = []) {
|
|
11430
|
-
super(message);
|
|
11431
|
-
this.issues = issues;
|
|
11432
|
-
}
|
|
11433
|
-
issues;
|
|
11434
|
-
name = "ImportError";
|
|
11435
|
-
};
|
|
11436
|
-
|
|
11437
12053
|
// ../core/src/import/curl.ts
|
|
11438
|
-
var ANSI_C = {
|
|
12054
|
+
var ANSI_C = {
|
|
12055
|
+
n: "\n",
|
|
12056
|
+
t: " ",
|
|
12057
|
+
r: "\r",
|
|
12058
|
+
a: "\x07",
|
|
12059
|
+
b: "\b",
|
|
12060
|
+
e: "\x1B",
|
|
12061
|
+
f: "\f",
|
|
12062
|
+
v: "\v",
|
|
12063
|
+
"\\": "\\",
|
|
12064
|
+
"'": "'",
|
|
12065
|
+
'"': '"',
|
|
12066
|
+
"?": "?"
|
|
12067
|
+
};
|
|
11439
12068
|
function ansiEscape(text3, at) {
|
|
11440
12069
|
const numbered = [
|
|
11441
12070
|
[/^x([0-9a-fA-F]{1,2})/, 16],
|
|
@@ -11546,6 +12175,7 @@ var VALUE_OPTIONS = {
|
|
|
11546
12175
|
"--data-raw": "data-raw",
|
|
11547
12176
|
"--data-binary": "data-binary",
|
|
11548
12177
|
"--data-urlencode": "data-urlencode",
|
|
12178
|
+
"--json": "json",
|
|
11549
12179
|
"-F": "form",
|
|
11550
12180
|
"--form": "form",
|
|
11551
12181
|
"--form-string": "form-string",
|
|
@@ -11568,7 +12198,33 @@ var FLAGS = {
|
|
|
11568
12198
|
"--insecure": "insecure",
|
|
11569
12199
|
"--compressed": "compressed"
|
|
11570
12200
|
};
|
|
11571
|
-
var QUIET_FLAGS = /* @__PURE__ */ new Set([
|
|
12201
|
+
var QUIET_FLAGS = /* @__PURE__ */ new Set([
|
|
12202
|
+
"-s",
|
|
12203
|
+
"--silent",
|
|
12204
|
+
"-S",
|
|
12205
|
+
"--show-error",
|
|
12206
|
+
"-v",
|
|
12207
|
+
"--verbose",
|
|
12208
|
+
"-i",
|
|
12209
|
+
"--include",
|
|
12210
|
+
"-L",
|
|
12211
|
+
"--location",
|
|
12212
|
+
"-f",
|
|
12213
|
+
"--fail",
|
|
12214
|
+
"-#",
|
|
12215
|
+
"--progress-bar",
|
|
12216
|
+
"-g",
|
|
12217
|
+
"--globoff",
|
|
12218
|
+
"-N",
|
|
12219
|
+
"--no-buffer",
|
|
12220
|
+
"--http1.1",
|
|
12221
|
+
"--http2",
|
|
12222
|
+
"-4",
|
|
12223
|
+
"-6",
|
|
12224
|
+
"-q",
|
|
12225
|
+
"-n",
|
|
12226
|
+
"-j"
|
|
12227
|
+
]);
|
|
11572
12228
|
var SKIPPED_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
11573
12229
|
"-o",
|
|
11574
12230
|
"--output",
|
|
@@ -11625,7 +12281,16 @@ var SKIPPED_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
11625
12281
|
"--trace-ascii",
|
|
11626
12282
|
"--stderr",
|
|
11627
12283
|
"--proto",
|
|
11628
|
-
"--proto-default"
|
|
12284
|
+
"--proto-default",
|
|
12285
|
+
"--url-query",
|
|
12286
|
+
"--proxy-header",
|
|
12287
|
+
"--socks5",
|
|
12288
|
+
"--socks5-hostname",
|
|
12289
|
+
"--preproxy",
|
|
12290
|
+
"--request-target",
|
|
12291
|
+
"--doh-url",
|
|
12292
|
+
"--output-dir",
|
|
12293
|
+
"--netrc-file"
|
|
11629
12294
|
]);
|
|
11630
12295
|
var formEncode = (text3) => new URLSearchParams([[text3, ""]]).toString().slice(0, -1);
|
|
11631
12296
|
var contentType = (headers2) => headers2.filter(([name]) => name.toLowerCase() === "content-type").at(-1)?.[1];
|
|
@@ -11650,7 +12315,9 @@ function formPart(value, warnings) {
|
|
|
11650
12315
|
if (quotedFile) return { name, fileName: quotedFile[1].replace(/\\(.)/g, "$1") };
|
|
11651
12316
|
if (content.startsWith("@")) return { name, fileName: content.slice(1).split(/[;,]/)[0] };
|
|
11652
12317
|
if (content.startsWith("<")) {
|
|
11653
|
-
warnings.push(
|
|
12318
|
+
warnings.push(
|
|
12319
|
+
`The form part ${name} takes its text from the file ${content.slice(1).split(";")[0]}, which was not read: the part is empty.`
|
|
12320
|
+
);
|
|
11654
12321
|
return { name, value: "" };
|
|
11655
12322
|
}
|
|
11656
12323
|
return { name, value: content.replace(/;type=[^;]*$/, "") };
|
|
@@ -11665,7 +12332,7 @@ function parseCurl(text3) {
|
|
|
11665
12332
|
const urls = [];
|
|
11666
12333
|
let method;
|
|
11667
12334
|
let basic;
|
|
11668
|
-
let [get, head, binaryOnly] = [false, false, true];
|
|
12335
|
+
let [get, head, binaryOnly, json] = [false, false, true, false];
|
|
11669
12336
|
const take = (kind, value) => {
|
|
11670
12337
|
switch (kind) {
|
|
11671
12338
|
case "method":
|
|
@@ -11680,7 +12347,9 @@ function parseCurl(text3) {
|
|
|
11680
12347
|
}
|
|
11681
12348
|
case "data":
|
|
11682
12349
|
case "data-binary":
|
|
11683
|
-
|
|
12350
|
+
case "json":
|
|
12351
|
+
if (kind === "json") json = true;
|
|
12352
|
+
if (kind !== "data-binary") binaryOnly = false;
|
|
11684
12353
|
data.push(value.startsWith("@") ? { file: value.slice(1) } : { text: value });
|
|
11685
12354
|
break;
|
|
11686
12355
|
case "data-raw":
|
|
@@ -11733,7 +12402,8 @@ function parseCurl(text3) {
|
|
|
11733
12402
|
} else if (word.startsWith("--")) {
|
|
11734
12403
|
if (Object.hasOwn(VALUE_OPTIONS, word)) take(VALUE_OPTIONS[word], valueOf(word));
|
|
11735
12404
|
else if (Object.hasOwn(FLAGS, word)) ({ get, head } = flag(FLAGS[word], { get, head }, warnings));
|
|
11736
|
-
else if (SKIPPED_VALUE_OPTIONS.has(word))
|
|
12405
|
+
else if (SKIPPED_VALUE_OPTIONS.has(word))
|
|
12406
|
+
warnings.push(`${word} ${valueOf(word)} was left out: the app has nothing it applies to.`);
|
|
11737
12407
|
else if (!QUIET_FLAGS.has(word)) warnings.push(`${word} was left out: the app does not know this option.`);
|
|
11738
12408
|
} else {
|
|
11739
12409
|
for (let at = 1; at < word.length; at++) {
|
|
@@ -11753,14 +12423,23 @@ function parseCurl(text3) {
|
|
|
11753
12423
|
}
|
|
11754
12424
|
}
|
|
11755
12425
|
if (!urls.length) throw new ImportError("The cURL command has no address.");
|
|
11756
|
-
if (urls.length > 1)
|
|
12426
|
+
if (urls.length > 1)
|
|
12427
|
+
warnings.push(
|
|
12428
|
+
`The command names ${urls.length} addresses. The first is used; the others were left out: ${urls.slice(1).join(", ")}.`
|
|
12429
|
+
);
|
|
11757
12430
|
let url = urls[0];
|
|
12431
|
+
if (json) {
|
|
12432
|
+
for (const name of ["Content-Type", "Accept"])
|
|
12433
|
+
if (!headers2.some(([named2]) => named2.toLowerCase() === name.toLowerCase()))
|
|
12434
|
+
headers2.push([name, "application/json"]);
|
|
12435
|
+
}
|
|
11758
12436
|
const type = (contentType(headers2) ?? "").toLowerCase();
|
|
11759
12437
|
let body3 = { mode: "none" };
|
|
11760
12438
|
const files = data.flatMap((datum) => "file" in datum ? [datum.file] : []);
|
|
11761
12439
|
if (parts.length) {
|
|
11762
12440
|
body3 = { mode: "multipart", parts };
|
|
11763
|
-
if (data.length)
|
|
12441
|
+
if (data.length)
|
|
12442
|
+
warnings.push("The command has both a form (-F) and data (-d). curl refuses that; the form is kept.");
|
|
11764
12443
|
} else if (get) {
|
|
11765
12444
|
const query = data.flatMap((datum) => "text" in datum ? [datum.text] : []).join("&");
|
|
11766
12445
|
if (files.length) warnings.push(`The data of the file ${files.join(", ")} was left out: the file was not read.`);
|
|
@@ -11770,17 +12449,26 @@ function parseCurl(text3) {
|
|
|
11770
12449
|
} else if (data.length) {
|
|
11771
12450
|
if (files.length) warnings.push(`The data of the file ${files.join(", ")} was left out: the file was not read.`);
|
|
11772
12451
|
const wire = data.flatMap((datum) => "text" in datum ? [datum.text] : []).join("&");
|
|
11773
|
-
|
|
12452
|
+
const pairs2 = wire.split("&").every((piece) => /^[\w.~%+\-[\]]*(=|$)/.test(piece));
|
|
12453
|
+
if (!type && !pairs2) headers2.push(["Content-Type", "application/x-www-form-urlencoded"]);
|
|
12454
|
+
body3 = !type && pairs2 || type.startsWith("application/x-www-form-urlencoded") ? { mode: "urlencoded", fields: [...new URLSearchParams(wire)] } : { mode: "raw", text: wire };
|
|
11774
12455
|
}
|
|
11775
12456
|
method ??= head ? "HEAD" : body3.mode === "none" ? "GET" : "POST";
|
|
11776
|
-
if (!isHttpMethod(method))
|
|
12457
|
+
if (!isHttpMethod(method))
|
|
12458
|
+
throw new ImportError(
|
|
12459
|
+
`The cURL command's method, ${method}, is not an HTTP method: one word of at most 32 characters.`
|
|
12460
|
+
);
|
|
11777
12461
|
return { request: { method, url, headers: headers2, body: body3 }, ...basic && { basic }, warnings };
|
|
11778
12462
|
}
|
|
11779
12463
|
function flag(kind, state, warnings) {
|
|
11780
12464
|
if (kind === "get") return { ...state, get: true };
|
|
11781
12465
|
if (kind === "head") return { ...state, head: true };
|
|
11782
|
-
if (kind === "insecure")
|
|
11783
|
-
|
|
12466
|
+
if (kind === "insecure")
|
|
12467
|
+
warnings.push(
|
|
12468
|
+
"-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."
|
|
12469
|
+
);
|
|
12470
|
+
if (kind === "compressed")
|
|
12471
|
+
warnings.push("--compressed was left out: the browser asks for a compressed answer by itself.");
|
|
11784
12472
|
return state;
|
|
11785
12473
|
}
|
|
11786
12474
|
function languageOf(type) {
|
|
@@ -11799,8 +12487,15 @@ function curlRequest(parsed2) {
|
|
|
11799
12487
|
const language = languageOf(type.toLowerCase());
|
|
11800
12488
|
const row = (key, value) => ({ key, value, enabled: true });
|
|
11801
12489
|
const implied = body3.mode === "urlencoded" ? "application/x-www-form-urlencoded" : body3.mode === "multipart" ? "multipart/form-data" : body3.mode === "raw" ? RAW_CONTENT_TYPES[language] : null;
|
|
11802
|
-
const headers2 = request.headers.filter(
|
|
11803
|
-
|
|
12490
|
+
const headers2 = request.headers.filter(
|
|
12491
|
+
([name, value]) => !(name.toLowerCase() === "content-type" && implied !== null && value.toLowerCase().split(";")[0].trim() === implied)
|
|
12492
|
+
);
|
|
12493
|
+
const content = {
|
|
12494
|
+
...emptyRequest(),
|
|
12495
|
+
method: request.method,
|
|
12496
|
+
url: request.url,
|
|
12497
|
+
headers: headers2.map(([name, value]) => row(name, value))
|
|
12498
|
+
};
|
|
11804
12499
|
if (basic) content.auth = { type: "basic", ...basic };
|
|
11805
12500
|
if (body3.mode === "raw") {
|
|
11806
12501
|
content.body = { mode: "raw", raw: { language, text: body3.text } };
|
|
@@ -11809,12 +12504,23 @@ function curlRequest(parsed2) {
|
|
|
11809
12504
|
} else if (body3.mode === "multipart") {
|
|
11810
12505
|
content.body = {
|
|
11811
12506
|
mode: "multipart",
|
|
11812
|
-
multipart: body3.parts.map(
|
|
12507
|
+
multipart: body3.parts.map(
|
|
12508
|
+
(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 }
|
|
12509
|
+
)
|
|
11813
12510
|
};
|
|
11814
|
-
for (const part of body3.parts)
|
|
12511
|
+
for (const part of body3.parts)
|
|
12512
|
+
if ("fileName" in part)
|
|
12513
|
+
warnings.push(
|
|
12514
|
+
`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.`
|
|
12515
|
+
);
|
|
11815
12516
|
} else if (body3.mode === "binary") {
|
|
11816
|
-
content.body = {
|
|
11817
|
-
|
|
12517
|
+
content.body = {
|
|
12518
|
+
mode: "binary",
|
|
12519
|
+
...type && { binary: { fileId: "", fileName: body3.fileName, contentType: type } }
|
|
12520
|
+
};
|
|
12521
|
+
warnings.push(
|
|
12522
|
+
`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.`
|
|
12523
|
+
);
|
|
11818
12524
|
}
|
|
11819
12525
|
return { content, warnings };
|
|
11820
12526
|
}
|
|
@@ -11835,16 +12541,23 @@ function bodyOf(postData, warnings) {
|
|
|
11835
12541
|
const params = list(own2(postData, "params"));
|
|
11836
12542
|
if (type.startsWith("multipart/form-data")) {
|
|
11837
12543
|
if (!params.length) {
|
|
11838
|
-
warnings.push(
|
|
12544
|
+
warnings.push(
|
|
12545
|
+
"Its multipart body is in the file as text only, with a boundary of that one send. It is kept as text."
|
|
12546
|
+
);
|
|
11839
12547
|
return { mode: "raw", text: text(own2(postData, "text")) };
|
|
11840
12548
|
}
|
|
11841
12549
|
return {
|
|
11842
12550
|
mode: "multipart",
|
|
11843
|
-
parts: params.map(
|
|
12551
|
+
parts: params.map(
|
|
12552
|
+
(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")) }
|
|
12553
|
+
)
|
|
11844
12554
|
};
|
|
11845
12555
|
}
|
|
11846
12556
|
if (type.startsWith("application/x-www-form-urlencoded")) {
|
|
11847
|
-
return {
|
|
12557
|
+
return {
|
|
12558
|
+
mode: "urlencoded",
|
|
12559
|
+
fields: params.length ? params.map((param) => [text(own2(param, "name")), text(own2(param, "value"))]) : [...new URLSearchParams(text(own2(postData, "text")))]
|
|
12560
|
+
};
|
|
11848
12561
|
}
|
|
11849
12562
|
const body3 = text(own2(postData, "text"));
|
|
11850
12563
|
if (own2(postData, "encoding") === "base64") {
|
|
@@ -11859,11 +12572,18 @@ function parseHar(value) {
|
|
|
11859
12572
|
return entries.flatMap((entry, index) => {
|
|
11860
12573
|
const request = own2(entry, "request");
|
|
11861
12574
|
const [method, url] = isObject3(request) ? [text(own2(request, "method")).toUpperCase(), text(own2(request, "url"))] : ["", ""];
|
|
11862
|
-
if (!isObject3(request) || !method || !url)
|
|
12575
|
+
if (!isObject3(request) || !method || !url)
|
|
12576
|
+
throw new ImportError(`The HAR's entry ${index + 1} has no request with a method and a URL.`);
|
|
11863
12577
|
const warnings = [];
|
|
11864
12578
|
const headers2 = list(own2(request, "headers")).map((header) => [text(own2(header, "name")), text(own2(header, "value"))]).filter(([name]) => name && !name.startsWith(":"));
|
|
11865
12579
|
const path = URL.canParse(url) ? new URL(url).pathname : url;
|
|
11866
|
-
return [
|
|
12580
|
+
return [
|
|
12581
|
+
{
|
|
12582
|
+
name: `${method} ${path}`,
|
|
12583
|
+
request: { method, url, headers: headers2, body: bodyOf(own2(request, "postData"), warnings) },
|
|
12584
|
+
warnings
|
|
12585
|
+
}
|
|
12586
|
+
];
|
|
11867
12587
|
});
|
|
11868
12588
|
}
|
|
11869
12589
|
|
|
@@ -11896,7 +12616,12 @@ function addressParts(raw) {
|
|
|
11896
12616
|
const host = colon === -1 ? authority : authority.slice(0, colon);
|
|
11897
12617
|
return {
|
|
11898
12618
|
...protocol !== void 0 && { protocol: back(protocol) },
|
|
11899
|
-
...user !== void 0 && {
|
|
12619
|
+
...user !== void 0 && {
|
|
12620
|
+
auth: {
|
|
12621
|
+
user: back(user.split(":")[0]),
|
|
12622
|
+
...user.includes(":") && { password: back(user.slice(user.indexOf(":") + 1)) }
|
|
12623
|
+
}
|
|
12624
|
+
},
|
|
11900
12625
|
host: host.startsWith("[") ? [back(host)] : host.split(".").map(back),
|
|
11901
12626
|
...port !== void 0 && { port: back(port) },
|
|
11902
12627
|
...path !== void 0 && { path: path.split("/").map(back) },
|
|
@@ -11931,7 +12656,11 @@ var description = (value) => {
|
|
|
11931
12656
|
const words = isObject4(value) ? text2(field3(value, "content")) : text2(value);
|
|
11932
12657
|
return words ? { description: words } : {};
|
|
11933
12658
|
};
|
|
11934
|
-
var rows = (value) => objects(value).map((entry) => ({
|
|
12659
|
+
var rows = (value) => objects(value).map((entry) => ({
|
|
12660
|
+
key: text2(field3(entry, "key") ?? field3(entry, "id")),
|
|
12661
|
+
value: text2(field3(entry, "value")),
|
|
12662
|
+
enabled: field3(entry, "disabled") !== true
|
|
12663
|
+
}));
|
|
11935
12664
|
function headers(value) {
|
|
11936
12665
|
if (typeof value !== "string") return rows(value);
|
|
11937
12666
|
return value.split(/\r?\n/).filter((line2) => line2.trim()).map((line2) => {
|
|
@@ -11956,15 +12685,30 @@ function auth(value, item, warnings, top) {
|
|
|
11956
12685
|
case "apikey":
|
|
11957
12686
|
return { type, key: read("key"), value: read("value"), in: read("in") === "query" ? "query" : "header" };
|
|
11958
12687
|
case "awsv4":
|
|
11959
|
-
return {
|
|
12688
|
+
return {
|
|
12689
|
+
type,
|
|
12690
|
+
accessKey: read("accessKey"),
|
|
12691
|
+
secretKey: read("secretKey"),
|
|
12692
|
+
sessionToken: read("sessionToken"),
|
|
12693
|
+
region: read("region"),
|
|
12694
|
+
service: read("service")
|
|
12695
|
+
};
|
|
11960
12696
|
case "oauth2": {
|
|
11961
12697
|
const grant = OAUTH_GRANTS_OF.get(read("grant_type"));
|
|
11962
12698
|
if (!grant) {
|
|
11963
|
-
warnings.push({
|
|
12699
|
+
warnings.push({
|
|
12700
|
+
item,
|
|
12701
|
+
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.`
|
|
12702
|
+
});
|
|
11964
12703
|
return { type: "none" };
|
|
11965
12704
|
}
|
|
11966
|
-
if (read("accessToken"))
|
|
11967
|
-
|
|
12705
|
+
if (read("accessToken"))
|
|
12706
|
+
warnings.push({ item, message: "The OAuth 2.0 token in the file is not kept: get one in the Auth tab." });
|
|
12707
|
+
if (read("addTokenTo") === "queryParams")
|
|
12708
|
+
warnings.push({
|
|
12709
|
+
item,
|
|
12710
|
+
message: "Its OAuth 2.0 token was added to the address: the app sends it in the Authorization header."
|
|
12711
|
+
});
|
|
11968
12712
|
return {
|
|
11969
12713
|
type,
|
|
11970
12714
|
grant,
|
|
@@ -11980,7 +12724,10 @@ function auth(value, item, warnings, top) {
|
|
|
11980
12724
|
};
|
|
11981
12725
|
}
|
|
11982
12726
|
default:
|
|
11983
|
-
warnings.push({
|
|
12727
|
+
warnings.push({
|
|
12728
|
+
item,
|
|
12729
|
+
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.`
|
|
12730
|
+
});
|
|
11984
12731
|
return { type: "none" };
|
|
11985
12732
|
}
|
|
11986
12733
|
}
|
|
@@ -11998,21 +12745,30 @@ function scripts(value, item, warnings) {
|
|
|
11998
12745
|
return Array.isArray(exec) ? exec.map(text2).join("\n") : text2(exec);
|
|
11999
12746
|
}).join("\n");
|
|
12000
12747
|
const others = objects(value).map((event) => text2(field3(event, "listen"))).filter((listen) => listen !== "prerequest" && listen !== "test");
|
|
12001
|
-
if (others.length)
|
|
12748
|
+
if (others.length)
|
|
12749
|
+
warnings.push({
|
|
12750
|
+
item,
|
|
12751
|
+
message: `A script for "${others[0]}" is left out: the app runs a script before a request and after its response.`
|
|
12752
|
+
});
|
|
12002
12753
|
return { pre: of2("prerequest"), post: of2("test") };
|
|
12003
12754
|
}
|
|
12004
12755
|
function address(value, item, warnings) {
|
|
12005
12756
|
const inText = (url2) => (addressParts(url2).query ?? []).map((entry) => ({ key: entry.key, value: entry.value ?? "", enabled: true }));
|
|
12006
12757
|
if (!isObject4(value)) return { url: text2(value), params: inText(text2(value)) };
|
|
12007
|
-
const kept = (part, marks) => part.split(/(\{\{[^{}]*\}\})/).map(
|
|
12758
|
+
const kept = (part, marks) => part.split(/(\{\{[^{}]*\}\})/).map(
|
|
12759
|
+
(piece, index) => index % 2 ? piece : piece.replace(marks, (mark) => `%${mark.charCodeAt(0).toString(16).toUpperCase()}`)
|
|
12760
|
+
).join("");
|
|
12008
12761
|
const params = objects(field3(value, "query")).map((entry) => {
|
|
12009
12762
|
const enabled = field3(entry, "disabled") !== true;
|
|
12010
|
-
return {
|
|
12763
|
+
return {
|
|
12764
|
+
key: enabled ? kept(text2(field3(entry, "key")), /[#&=]/g) : text2(field3(entry, "key")),
|
|
12765
|
+
value: enabled ? kept(text2(field3(entry, "value")), /[#&]/g) : text2(field3(entry, "value")),
|
|
12766
|
+
enabled
|
|
12767
|
+
};
|
|
12011
12768
|
});
|
|
12012
|
-
const off = params.filter((row) => !row.enabled).map((row) => row.key);
|
|
12013
|
-
if (off.length) warnings.push({ item, message: `${off.length === 1 ? `A query parameter that is switched off (${off[0]}) is` : `${off.length} query parameters that are switched off are`} kept for an export, but the composer has no switch for a parameter: it does not show one, and saving the request there drops it.` });
|
|
12014
12769
|
const host = field3(value, "host");
|
|
12015
|
-
if (host === void 0)
|
|
12770
|
+
if (host === void 0)
|
|
12771
|
+
return { url: text2(field3(value, "raw")), params: params.length ? params : inText(text2(field3(value, "raw"))) };
|
|
12016
12772
|
const variables2 = objects(field3(value, "variable"));
|
|
12017
12773
|
const replaced = [];
|
|
12018
12774
|
const named2 = [];
|
|
@@ -12029,11 +12785,21 @@ function address(value, item, warnings) {
|
|
|
12029
12785
|
const protocol = field3(value, "protocol");
|
|
12030
12786
|
const port = field3(value, "port");
|
|
12031
12787
|
const path = field3(value, "path");
|
|
12032
|
-
const query = objects(field3(value, "query")).filter((entry) => field3(entry, "disabled") !== true).map(
|
|
12788
|
+
const query = objects(field3(value, "query")).filter((entry) => field3(entry, "disabled") !== true).map(
|
|
12789
|
+
(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)}`
|
|
12790
|
+
);
|
|
12033
12791
|
const hash = field3(value, "hash");
|
|
12034
12792
|
const url = (protocol === void 0 ? "" : `${text2(protocol)}://`) + (isObject4(login) ? `${text2(field3(login, "user"))}${field3(login, "password") === void 0 ? "" : `:${text2(field3(login, "password"))}`}@` : "") + joined(host, ".") + (port === void 0 ? "" : `:${text2(port)}`) + (path === void 0 ? "" : `/${Array.isArray(path) ? joined(path, "/", inPath) : text2(path).replace(/^\//, "").split("/").map(inPath).join("/")}`) + (query.length ? `?${query.join("&")}` : "") + (hash === void 0 ? "" : `#${text2(hash)}`);
|
|
12035
|
-
if (replaced.length)
|
|
12036
|
-
|
|
12793
|
+
if (replaced.length)
|
|
12794
|
+
warnings.push({
|
|
12795
|
+
item,
|
|
12796
|
+
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.`
|
|
12797
|
+
});
|
|
12798
|
+
if (named2.length)
|
|
12799
|
+
warnings.push({
|
|
12800
|
+
item,
|
|
12801
|
+
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"}.`
|
|
12802
|
+
});
|
|
12037
12803
|
return { url, params };
|
|
12038
12804
|
}
|
|
12039
12805
|
function body(value, item, warnings) {
|
|
@@ -12053,11 +12819,19 @@ function body(value, item, warnings) {
|
|
|
12053
12819
|
return field3(part, "type") === "file" ? { key, enabled, kind: "file", fileId: "", fileName: file(field3(part, "src")) } : { key, enabled, kind: "text", value: text2(field3(part, "value")) };
|
|
12054
12820
|
})
|
|
12055
12821
|
},
|
|
12056
|
-
...isObject4(field3(value, "file")) && {
|
|
12822
|
+
...isObject4(field3(value, "file")) && {
|
|
12823
|
+
binary: { fileId: "", fileName: file(field3(field3(value, "file"), "src")) }
|
|
12824
|
+
},
|
|
12057
12825
|
// Postman keeps the variables as text, as the app does.
|
|
12058
|
-
...isObject4(field3(value, "graphql")) && {
|
|
12826
|
+
...isObject4(field3(value, "graphql")) && {
|
|
12827
|
+
graphql: {
|
|
12828
|
+
query: text2(field3(field3(value, "graphql"), "query")),
|
|
12829
|
+
variables: text2(field3(field3(value, "graphql"), "variables"))
|
|
12830
|
+
}
|
|
12831
|
+
}
|
|
12059
12832
|
};
|
|
12060
|
-
if (kept.multipart?.some((part) => part.kind === "file") || kept.binary)
|
|
12833
|
+
if (kept.multipart?.some((part) => part.kind === "file") || kept.binary)
|
|
12834
|
+
warnings.push({ item, message: "A file of its body is named, not included: choose it again in the Body tab." });
|
|
12061
12835
|
if (field3(value, "disabled") === true) return { mode: "none", ...kept };
|
|
12062
12836
|
switch (field3(value, "mode")) {
|
|
12063
12837
|
case "raw":
|
|
@@ -12075,7 +12849,10 @@ function body(value, item, warnings) {
|
|
|
12075
12849
|
case "none":
|
|
12076
12850
|
return { mode: "none", ...kept };
|
|
12077
12851
|
default:
|
|
12078
|
-
warnings.push({
|
|
12852
|
+
warnings.push({
|
|
12853
|
+
item,
|
|
12854
|
+
message: `Its body is of a kind the app does not have (${text2(field3(value, "mode"))}) and is left out.`
|
|
12855
|
+
});
|
|
12079
12856
|
return { mode: "none", ...kept };
|
|
12080
12857
|
}
|
|
12081
12858
|
}
|
|
@@ -12120,7 +12897,10 @@ function postmanCollection(value) {
|
|
|
12120
12897
|
const method = text2(field3(source, "method") ?? "GET").toUpperCase();
|
|
12121
12898
|
examples += list2(field3(entry, "response")).length;
|
|
12122
12899
|
if (!url.trim() || !isHttpMethod(method)) {
|
|
12123
|
-
warnings.push({
|
|
12900
|
+
warnings.push({
|
|
12901
|
+
item: item2,
|
|
12902
|
+
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.`
|
|
12903
|
+
});
|
|
12124
12904
|
continue;
|
|
12125
12905
|
}
|
|
12126
12906
|
requests.push({
|
|
@@ -12145,7 +12925,11 @@ function postmanCollection(value) {
|
|
|
12145
12925
|
};
|
|
12146
12926
|
walk2(field3(value, "item"), void 0, 0);
|
|
12147
12927
|
const item = `collection "${name}"`;
|
|
12148
|
-
if (examples)
|
|
12928
|
+
if (examples)
|
|
12929
|
+
warnings.push({
|
|
12930
|
+
item,
|
|
12931
|
+
message: `${examples} saved ${examples === 1 ? "response is" : "responses are"} left out: the app keeps responses in its history, not in a collection.`
|
|
12932
|
+
});
|
|
12149
12933
|
return {
|
|
12150
12934
|
payload: {
|
|
12151
12935
|
$id: COLLECTION_FORMAT,
|
|
@@ -12177,9 +12961,18 @@ function postmanEnvironment(value) {
|
|
|
12177
12961
|
meta: { id, createdAt: now, updatedAt: now },
|
|
12178
12962
|
name,
|
|
12179
12963
|
order: 0,
|
|
12180
|
-
vars: objects(isObject4(value) ? field3(value, "values") : value).map((entry) => ({
|
|
12964
|
+
vars: objects(isObject4(value) ? field3(value, "values") : value).map((entry) => ({
|
|
12965
|
+
key: text2(field3(entry, "key")),
|
|
12966
|
+
value: text2(field3(entry, "value")),
|
|
12967
|
+
enabled: field3(entry, "enabled") !== false && field3(entry, "disabled") !== true
|
|
12968
|
+
}))
|
|
12181
12969
|
},
|
|
12182
|
-
warnings: globals ? [
|
|
12970
|
+
warnings: globals ? [
|
|
12971
|
+
{
|
|
12972
|
+
item: `environment "${name}"`,
|
|
12973
|
+
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."
|
|
12974
|
+
}
|
|
12975
|
+
] : []
|
|
12183
12976
|
};
|
|
12184
12977
|
}
|
|
12185
12978
|
|
|
@@ -12190,10 +12983,16 @@ function isInsomnia(value) {
|
|
|
12190
12983
|
return field3(value, "_type") === "export" || Object.hasOwn(value, "__export_format") || /^\w+\.insomnia(\.rest)?\/\d/.test(text2(field3(value, "type")));
|
|
12191
12984
|
}
|
|
12192
12985
|
var VARIABLE = /\{\{\s*(?:_\.|_(?=\[))?((?:\w[\w-]*|\[(?:'[^'\]]*'|"[^"\]]*")\])(?:\.\w[\w-]*|\[(?:'[^'\]]*'|"[^"\]]*")\])*)\s*\}\}/g;
|
|
12193
|
-
var variables = (value) => value.replace(
|
|
12986
|
+
var variables = (value) => value.replace(
|
|
12987
|
+
VARIABLE,
|
|
12988
|
+
(_match, path) => `{{${path.replace(/\[(?:'([^'\]]*)'|"([^"\]]*)")\]/g, (_part, single, double) => `.${single ?? double ?? ""}`).replace(/^\./, "")}}}`
|
|
12989
|
+
);
|
|
12194
12990
|
function foreign(value) {
|
|
12195
12991
|
const found = [];
|
|
12196
|
-
for (const [open, close] of [
|
|
12992
|
+
for (const [open, close] of [
|
|
12993
|
+
["{%", "%}"],
|
|
12994
|
+
["{{", "}}"]
|
|
12995
|
+
]) {
|
|
12197
12996
|
let at = value.indexOf(open);
|
|
12198
12997
|
while (at !== -1) {
|
|
12199
12998
|
const end = value.indexOf(close, at + 2);
|
|
@@ -12214,7 +13013,11 @@ function reader(item, warnings) {
|
|
|
12214
13013
|
return turned;
|
|
12215
13014
|
},
|
|
12216
13015
|
done: () => {
|
|
12217
|
-
if (templates.length)
|
|
13016
|
+
if (templates.length)
|
|
13017
|
+
warnings.push({
|
|
13018
|
+
item,
|
|
13019
|
+
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.`
|
|
13020
|
+
});
|
|
12218
13021
|
}
|
|
12219
13022
|
};
|
|
12220
13023
|
}
|
|
@@ -12230,7 +13033,11 @@ function environment(name, vars) {
|
|
|
12230
13033
|
const now = Date.now();
|
|
12231
13034
|
return { id, meta: { id, createdAt: now, updatedAt: now }, name, order: 0, vars };
|
|
12232
13035
|
}
|
|
12233
|
-
var pairs = (value, said2) => objects(value).map((entry) => ({
|
|
13036
|
+
var pairs = (value, said2) => objects(value).map((entry) => ({
|
|
13037
|
+
key: said2(field3(entry, "name")),
|
|
13038
|
+
value: said2(field3(entry, "value")),
|
|
13039
|
+
...field3(entry, "disabled") === true && { disabled: true }
|
|
13040
|
+
})).filter((entry) => entry.key !== "" || entry.value !== "");
|
|
12234
13041
|
function auth2(value, said2) {
|
|
12235
13042
|
if (!isObject4(value) || typeof field3(value, "type") !== "string" || field3(value, "disabled") === true) return {};
|
|
12236
13043
|
const type = text2(field3(value, "type"));
|
|
@@ -12243,18 +13050,44 @@ function auth2(value, said2) {
|
|
|
12243
13050
|
case "bearer": {
|
|
12244
13051
|
const prefix = read("prefix").trim();
|
|
12245
13052
|
if (!prefix || prefix === "Bearer") return { auth: { type, bearer: { token: read("token") } } };
|
|
12246
|
-
return {
|
|
13053
|
+
return {
|
|
13054
|
+
auth: {
|
|
13055
|
+
type: "apikey",
|
|
13056
|
+
apikey: {
|
|
13057
|
+
key: "Authorization",
|
|
13058
|
+
value: prefix === "NO_PREFIX" ? read("token") : `${prefix} ${read("token")}`,
|
|
13059
|
+
in: "header"
|
|
13060
|
+
}
|
|
13061
|
+
}
|
|
13062
|
+
};
|
|
12247
13063
|
}
|
|
12248
13064
|
case "apikey": {
|
|
12249
13065
|
const place = text2(field3(value, "addTo"));
|
|
12250
13066
|
if (place && place !== "header" && place !== "queryParams") return { auth: { type: `an API key in a ${place}` } };
|
|
12251
|
-
return {
|
|
13067
|
+
return {
|
|
13068
|
+
auth: {
|
|
13069
|
+
type,
|
|
13070
|
+
apikey: { key: read("key"), value: read("value"), in: place === "queryParams" ? "query" : "header" }
|
|
13071
|
+
}
|
|
13072
|
+
};
|
|
12252
13073
|
}
|
|
12253
13074
|
case "iam":
|
|
12254
|
-
return {
|
|
13075
|
+
return {
|
|
13076
|
+
auth: {
|
|
13077
|
+
type: "awsv4",
|
|
13078
|
+
awsv4: {
|
|
13079
|
+
accessKey: read("accessKeyId"),
|
|
13080
|
+
secretKey: read("secretAccessKey"),
|
|
13081
|
+
sessionToken: read("sessionToken"),
|
|
13082
|
+
region: read("region"),
|
|
13083
|
+
service: read("service")
|
|
13084
|
+
}
|
|
13085
|
+
}
|
|
13086
|
+
};
|
|
12255
13087
|
case "oauth2": {
|
|
12256
13088
|
const grant = read("grantType");
|
|
12257
|
-
if (grant !== "authorization_code" && grant !== "client_credentials" && grant !== "password")
|
|
13089
|
+
if (grant !== "authorization_code" && grant !== "client_credentials" && grant !== "password")
|
|
13090
|
+
return { auth: { type, oauth2: { grant_type: grant || "none said" } } };
|
|
12258
13091
|
return {
|
|
12259
13092
|
auth: {
|
|
12260
13093
|
type,
|
|
@@ -12291,8 +13124,10 @@ function body2(value, said2) {
|
|
|
12291
13124
|
}
|
|
12292
13125
|
};
|
|
12293
13126
|
}
|
|
12294
|
-
if (type === "application/x-www-form-urlencoded" && Array.isArray(params))
|
|
12295
|
-
|
|
13127
|
+
if (type === "application/x-www-form-urlencoded" && Array.isArray(params))
|
|
13128
|
+
return { body: { mode: "urlencoded", urlencoded: pairs(params, said2) } };
|
|
13129
|
+
if (typeof field3(value, "fileName") === "string" && typeof field3(value, "text") !== "string")
|
|
13130
|
+
return { body: { mode: "file", file: { src: field3(value, "fileName") } } };
|
|
12296
13131
|
if (typeof field3(value, "text") !== "string" || !type && !field3(value, "text")) return {};
|
|
12297
13132
|
const language = /json|graphql/.test(type) ? "json" : type.includes("xml") ? "xml" : type.includes("html") ? "html" : type.includes("javascript") ? "javascript" : "text";
|
|
12298
13133
|
return { body: { mode: "raw", raw: said2(field3(value, "text")), options: { raw: { language } } } };
|
|
@@ -12301,7 +13136,11 @@ function builder(warnings) {
|
|
|
12301
13136
|
const folderVariables = [];
|
|
12302
13137
|
const scripts2 = (item, { pre, post }) => {
|
|
12303
13138
|
const kinds = [text2(pre).trim() && "pre-request", text2(post).trim() && "after-response"].filter(Boolean);
|
|
12304
|
-
if (kinds.length)
|
|
13139
|
+
if (kinds.length)
|
|
13140
|
+
warnings.push({
|
|
13141
|
+
item,
|
|
13142
|
+
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.`
|
|
13143
|
+
});
|
|
12305
13144
|
};
|
|
12306
13145
|
const request = ({ source, name, description: description2, scripts: own3 }) => {
|
|
12307
13146
|
const item = `request "${name || "Request"}"`;
|
|
@@ -12313,7 +13152,11 @@ function builder(warnings) {
|
|
|
12313
13152
|
method: text2(field3(source, "method")),
|
|
12314
13153
|
description: description2,
|
|
12315
13154
|
// Insomnia holds the query's rows beside the address, and sends both.
|
|
12316
|
-
url: {
|
|
13155
|
+
url: {
|
|
13156
|
+
...url,
|
|
13157
|
+
query: [...url.query ?? [], ...pairs(field3(source, "parameters"), said2)],
|
|
13158
|
+
variable: pairs(field3(source, "pathParameters"), said2)
|
|
13159
|
+
},
|
|
12317
13160
|
header: pairs(field3(source, "headers"), said2),
|
|
12318
13161
|
...body2(field3(source, "body"), said2),
|
|
12319
13162
|
...auth2(field3(source, "authentication"), said2)
|
|
@@ -12329,7 +13172,14 @@ function builder(warnings) {
|
|
|
12329
13172
|
const variable = rowsOf(field3(source, "environment"), said2);
|
|
12330
13173
|
folderVariables.push(...variable.map(({ key }) => ({ item, key })));
|
|
12331
13174
|
const headers2 = pairs(field3(source, "headers"), said2).filter((header) => !header.disabled);
|
|
12332
|
-
if (headers2.length)
|
|
13175
|
+
if (headers2.length)
|
|
13176
|
+
warnings.push({
|
|
13177
|
+
item,
|
|
13178
|
+
message: `Its headers (${headers2.map((header) => header.key).join(", ").slice(
|
|
13179
|
+
0,
|
|
13180
|
+
200
|
|
13181
|
+
)}) 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.`
|
|
13182
|
+
});
|
|
12333
13183
|
scripts2(item, own3);
|
|
12334
13184
|
done();
|
|
12335
13185
|
return { name, description: description2, item: children, variable, ...auth2(field3(source, "authentication"), said2) };
|
|
@@ -12337,7 +13187,11 @@ function builder(warnings) {
|
|
|
12337
13187
|
const compare = (environments) => {
|
|
12338
13188
|
for (const { item, key } of folderVariables) {
|
|
12339
13189
|
const also = environments.find((entry) => entry.vars.some((row) => row.key === key));
|
|
12340
|
-
if (also)
|
|
13190
|
+
if (also)
|
|
13191
|
+
warnings.push({
|
|
13192
|
+
item,
|
|
13193
|
+
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.`
|
|
13194
|
+
});
|
|
12341
13195
|
}
|
|
12342
13196
|
};
|
|
12343
13197
|
return { request, folder, compare };
|
|
@@ -12349,7 +13203,10 @@ function format5(value) {
|
|
|
12349
13203
|
const tree = field3(value, "collection");
|
|
12350
13204
|
const held = field3(value, "environments");
|
|
12351
13205
|
const type = text2(field3(value, "type"));
|
|
12352
|
-
if (!Array.isArray(tree) && !isObject4(held))
|
|
13206
|
+
if (!Array.isArray(tree) && !isObject4(held))
|
|
13207
|
+
throw new ImportError(
|
|
13208
|
+
`This Insomnia file (${type.slice(0, 60)}) holds no requests and no environments, so there is nothing to import.`
|
|
13209
|
+
);
|
|
12353
13210
|
const name = text2(field3(value, "name")).trim() || UNNAMED;
|
|
12354
13211
|
const item = Array.isArray(tree) ? `collection "${name}"` : `environment "${name}"`;
|
|
12355
13212
|
const { said: said2, done } = reader(item, warnings);
|
|
@@ -12358,15 +13215,26 @@ function format5(value) {
|
|
|
12358
13215
|
if (depth > MAX_DEPTH) throw tooDeep();
|
|
12359
13216
|
return objects(items).flatMap((source) => {
|
|
12360
13217
|
const own3 = isObject4(field3(source, "scripts")) ? field3(source, "scripts") : {};
|
|
12361
|
-
const entry = {
|
|
13218
|
+
const entry = {
|
|
13219
|
+
source,
|
|
13220
|
+
name: text2(field3(source, "name")).trim(),
|
|
13221
|
+
description: described(source),
|
|
13222
|
+
scripts: { pre: field3(own3, "preRequest"), post: field3(own3, "afterResponse") }
|
|
13223
|
+
};
|
|
12362
13224
|
if (Array.isArray(field3(source, "children"))) return [folder(entry, level(field3(source, "children"), depth + 1))];
|
|
12363
13225
|
if (typeof field3(source, "method") === "string") return [request(entry)];
|
|
12364
|
-
warnings.push({
|
|
13226
|
+
warnings.push({
|
|
13227
|
+
item: `request "${entry.name || "Request"}"`,
|
|
13228
|
+
message: "It is not an HTTP request (gRPC, WebSocket or Socket.IO) and is left out."
|
|
13229
|
+
});
|
|
12365
13230
|
return [];
|
|
12366
13231
|
});
|
|
12367
13232
|
};
|
|
12368
13233
|
const base = isObject4(held) ? rowsOf(field3(held, "data"), said2) : [];
|
|
12369
|
-
const subs = objects(isObject4(held) ? field3(held, "subEnvironments") : []).map((sub) => ({
|
|
13234
|
+
const subs = objects(isObject4(held) ? field3(held, "subEnvironments") : []).map((sub) => ({
|
|
13235
|
+
name: text2(field3(sub, "name")).trim() || "Environment",
|
|
13236
|
+
vars: rowsOf(field3(sub, "data"), said2)
|
|
13237
|
+
}));
|
|
12370
13238
|
const spec = field3(value, "spec");
|
|
12371
13239
|
const contents = isObject4(spec) ? field3(spec, "contents") : void 0;
|
|
12372
13240
|
const left = [
|
|
@@ -12375,13 +13243,28 @@ function format5(value) {
|
|
|
12375
13243
|
objects(field3(value, "testSuites")).length > 0 && "unit tests",
|
|
12376
13244
|
isObject4(spec) && (typeof field3(spec, "file") === "string" || isObject4(contents) && Object.keys(contents).length > 0) && "API specification"
|
|
12377
13245
|
].filter((kind) => typeof kind === "string");
|
|
12378
|
-
if (left.length)
|
|
13246
|
+
if (left.length)
|
|
13247
|
+
warnings.push({
|
|
13248
|
+
item,
|
|
13249
|
+
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.`
|
|
13250
|
+
});
|
|
12379
13251
|
let result;
|
|
12380
13252
|
if (Array.isArray(tree)) {
|
|
12381
|
-
result = {
|
|
13253
|
+
result = {
|
|
13254
|
+
collections: [{ info: { name, description: described(value) }, variable: base, item: level(tree, 1) }],
|
|
13255
|
+
environments: subs.map((sub) => environment(sub.name, sub.vars)),
|
|
13256
|
+
warnings
|
|
13257
|
+
};
|
|
12382
13258
|
} else {
|
|
12383
|
-
const merged = (own3) => [
|
|
12384
|
-
|
|
13259
|
+
const merged = (own3) => [
|
|
13260
|
+
...own3,
|
|
13261
|
+
...base.filter((row) => !own3.some((other) => other.key === row.key))
|
|
13262
|
+
];
|
|
13263
|
+
result = {
|
|
13264
|
+
collections: [],
|
|
13265
|
+
environments: [environment(name, base), ...subs.map((sub) => environment(sub.name, merged(sub.vars)))],
|
|
13266
|
+
warnings
|
|
13267
|
+
};
|
|
12385
13268
|
}
|
|
12386
13269
|
done();
|
|
12387
13270
|
compare(result.environments);
|
|
@@ -12390,7 +13273,9 @@ function format5(value) {
|
|
|
12390
13273
|
function format4(value) {
|
|
12391
13274
|
const version = field3(value, "__export_format");
|
|
12392
13275
|
if (version !== 4) {
|
|
12393
|
-
throw new ImportError(
|
|
13276
|
+
throw new ImportError(
|
|
13277
|
+
`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.`
|
|
13278
|
+
);
|
|
12394
13279
|
}
|
|
12395
13280
|
const warnings = [];
|
|
12396
13281
|
const { request, folder, compare } = builder(warnings);
|
|
@@ -12401,7 +13286,9 @@ function format4(value) {
|
|
|
12401
13286
|
const children = /* @__PURE__ */ new Map();
|
|
12402
13287
|
for (const resource of resources) {
|
|
12403
13288
|
const parent = text2(field3(resource, "parentId"));
|
|
12404
|
-
|
|
13289
|
+
const siblings = children.get(parent);
|
|
13290
|
+
if (siblings) siblings.push(resource);
|
|
13291
|
+
else children.set(parent, [resource]);
|
|
12405
13292
|
}
|
|
12406
13293
|
for (const list4 of children.values()) list4.sort((a, b) => sortKey(a) - sortKey(b));
|
|
12407
13294
|
const under = (parent, type) => parent === "" ? [] : (children.get(parent) ?? []).filter((resource) => kind(resource) === type);
|
|
@@ -12411,7 +13298,12 @@ function format4(value) {
|
|
|
12411
13298
|
return (parent === "" ? [] : children.get(parent) ?? []).flatMap((source) => {
|
|
12412
13299
|
if (placed.has(source) || kind(source) !== "request" && kind(source) !== "request_group") return [];
|
|
12413
13300
|
placed.add(source);
|
|
12414
|
-
const entry = {
|
|
13301
|
+
const entry = {
|
|
13302
|
+
source,
|
|
13303
|
+
name: text2(field3(source, "name")).trim(),
|
|
13304
|
+
description: field3(source, "description"),
|
|
13305
|
+
scripts: { pre: field3(source, "preRequestScript"), post: field3(source, "afterResponseScript") }
|
|
13306
|
+
};
|
|
12415
13307
|
return [kind(source) === "request_group" ? folder(entry, level(id(source), depth + 1)) : request(entry)];
|
|
12416
13308
|
});
|
|
12417
13309
|
};
|
|
@@ -12419,7 +13311,11 @@ function format4(value) {
|
|
|
12419
13311
|
const collections = workspaces.map((workspace) => {
|
|
12420
13312
|
const name = text2(field3(workspace, "name")).trim() || UNNAMED;
|
|
12421
13313
|
const { said: said2, done } = reader(`collection "${name}"`, warnings);
|
|
12422
|
-
const collection = {
|
|
13314
|
+
const collection = {
|
|
13315
|
+
info: { name, description: field3(workspace, "description") },
|
|
13316
|
+
variable: under(id(workspace), "environment").flatMap((base) => rowsOf(field3(base, "data"), said2)),
|
|
13317
|
+
item: level(id(workspace), 1)
|
|
13318
|
+
};
|
|
12423
13319
|
done();
|
|
12424
13320
|
return collection;
|
|
12425
13321
|
});
|
|
@@ -12432,16 +13328,38 @@ function format4(value) {
|
|
|
12432
13328
|
done();
|
|
12433
13329
|
return made;
|
|
12434
13330
|
});
|
|
12435
|
-
const known = new Set(
|
|
13331
|
+
const known = new Set(
|
|
13332
|
+
resources.filter((resource) => kind(resource) === "workspace" || kind(resource) === "request_group").map(id)
|
|
13333
|
+
);
|
|
12436
13334
|
const loose = [...children.keys()].filter((parent) => parent !== "" && !known.has(parent)).flatMap((parent) => level(parent, 1));
|
|
12437
13335
|
if (loose.length) collections.push({ info: { name: UNNAMED, description: void 0 }, variable: [], item: loose });
|
|
12438
|
-
const unplaced = resources.filter(
|
|
12439
|
-
|
|
13336
|
+
const unplaced = resources.filter(
|
|
13337
|
+
(resource) => (kind(resource) === "request" || kind(resource) === "request_group") && !placed.has(resource)
|
|
13338
|
+
).length;
|
|
13339
|
+
if (unplaced)
|
|
13340
|
+
warnings.push({
|
|
13341
|
+
item: "file",
|
|
13342
|
+
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.`
|
|
13343
|
+
});
|
|
12440
13344
|
for (const resource of resources) {
|
|
12441
|
-
if (kind(resource).endsWith("_request"))
|
|
13345
|
+
if (kind(resource).endsWith("_request"))
|
|
13346
|
+
warnings.push({
|
|
13347
|
+
item: `request "${text2(field3(resource, "name")).trim() || "Request"}"`,
|
|
13348
|
+
message: "It is not an HTTP request (gRPC, WebSocket or Socket.IO) and is left out."
|
|
13349
|
+
});
|
|
12442
13350
|
}
|
|
12443
|
-
const others = [
|
|
12444
|
-
|
|
13351
|
+
const others = [
|
|
13352
|
+
...new Set(
|
|
13353
|
+
resources.filter(
|
|
13354
|
+
(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())
|
|
13355
|
+
).map((resource) => kind(resource).slice(0, 30))
|
|
13356
|
+
)
|
|
13357
|
+
];
|
|
13358
|
+
if (others.length)
|
|
13359
|
+
warnings.push({
|
|
13360
|
+
item: "file",
|
|
13361
|
+
message: `What the file holds that is not a request, a folder or an environment is left out (${others.slice(0, 8).join(", ")}).`
|
|
13362
|
+
});
|
|
12445
13363
|
compare(environments);
|
|
12446
13364
|
return { collections, environments, warnings };
|
|
12447
13365
|
}
|
|
@@ -12487,13 +13405,19 @@ function openApiCollection(root) {
|
|
|
12487
13405
|
const swagger = field3(root, "swagger") !== void 0;
|
|
12488
13406
|
const version = text2(field3(root, swagger ? "swagger" : "openapi")).slice(0, 20);
|
|
12489
13407
|
if (swagger ? !/^2(\.0)?$/.test(version) : !/^3(\.|$)/.test(version)) {
|
|
12490
|
-
throw new ImportError(
|
|
13408
|
+
throw new ImportError(
|
|
13409
|
+
`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.`
|
|
13410
|
+
);
|
|
12491
13411
|
}
|
|
12492
13412
|
const warnings = [];
|
|
12493
13413
|
const info = field3(root, "info");
|
|
12494
13414
|
const name = text2(isObject4(info) ? field3(info, "title") : "").trim() || UNNAMED2;
|
|
12495
13415
|
const top = `collection "${name}"`;
|
|
12496
|
-
if (!swagger && !/^3(\.[01](\.|$)|$)/.test(version))
|
|
13416
|
+
if (!swagger && !/^3(\.[01](\.|$)|$)/.test(version))
|
|
13417
|
+
warnings.push({
|
|
13418
|
+
item: top,
|
|
13419
|
+
message: `The file is written for OpenAPI ${version}: it is read as OpenAPI 3.1, and what is newer is left out.`
|
|
13420
|
+
});
|
|
12497
13421
|
let item = top;
|
|
12498
13422
|
const unread = /* @__PURE__ */ new Map();
|
|
12499
13423
|
const note = (kind, ref) => {
|
|
@@ -12542,7 +13466,8 @@ function openApiCollection(root) {
|
|
|
12542
13466
|
const of2 = (raw, depth) => {
|
|
12543
13467
|
const schema = resolve(raw);
|
|
12544
13468
|
if (!isObject4(schema) || left-- <= 0 || depth > MAX_DEPTH || open.has(schema)) return null;
|
|
12545
|
-
for (const key of ["example", "default", "const"])
|
|
13469
|
+
for (const key of ["example", "default", "const"])
|
|
13470
|
+
if (field3(schema, key) !== void 0) return field3(schema, key);
|
|
12546
13471
|
for (const key of ["examples", "enum"]) if (list3(field3(schema, key)).length) return list3(field3(schema, key))[0];
|
|
12547
13472
|
open.add(schema);
|
|
12548
13473
|
try {
|
|
@@ -12555,7 +13480,8 @@ function openApiCollection(root) {
|
|
|
12555
13480
|
});
|
|
12556
13481
|
parts.push(Object.fromEntries(sent.map((key) => [key, of2(field3(properties, key), depth + 1)])));
|
|
12557
13482
|
}
|
|
12558
|
-
if (parts.length)
|
|
13483
|
+
if (parts.length)
|
|
13484
|
+
return parts.some(isObject4) ? Object.fromEntries(parts.filter(isObject4).flatMap((part) => Object.entries(part))) : parts[0];
|
|
12559
13485
|
const choice = [...list3(field3(schema, "oneOf")), ...list3(field3(schema, "anyOf"))][0];
|
|
12560
13486
|
if (choice !== void 0) return of2(choice, depth + 1);
|
|
12561
13487
|
const types = field3(schema, "type");
|
|
@@ -12581,20 +13507,43 @@ function openApiCollection(root) {
|
|
|
12581
13507
|
const schema = resolve(raw);
|
|
12582
13508
|
return isObject4(schema) && (field3(schema, "type") === "file" || field3(schema, "format") === "binary");
|
|
12583
13509
|
};
|
|
12584
|
-
const form = (type, fields) => type.startsWith("multipart/") ? {
|
|
13510
|
+
const form = (type, fields) => type.startsWith("multipart/") ? {
|
|
13511
|
+
mode: "formdata",
|
|
13512
|
+
formdata: fields.map(
|
|
13513
|
+
({ key, schema, value }) => isFile(schema) ? { key, type: "file", src: "" } : { key, type: "text", value: said(value) }
|
|
13514
|
+
)
|
|
13515
|
+
} : { mode: "urlencoded", urlencoded: fields.map(({ key, value }) => ({ key, value: said(value) })) };
|
|
12585
13516
|
const bodyOf2 = (type, media) => {
|
|
12586
13517
|
const kind = type.toLowerCase();
|
|
12587
13518
|
const schema = resolve(field3(media, "schema"));
|
|
12588
13519
|
const value = example(media) ?? sample(schema, true);
|
|
12589
13520
|
if (kind.startsWith("multipart/") || kind === "application/x-www-form-urlencoded") {
|
|
12590
13521
|
const properties = isObject4(schema) ? field3(schema, "properties") : void 0;
|
|
12591
|
-
return {
|
|
13522
|
+
return {
|
|
13523
|
+
body: form(
|
|
13524
|
+
kind,
|
|
13525
|
+
Object.keys(isObject4(value) ? value : {}).map((key) => ({
|
|
13526
|
+
key,
|
|
13527
|
+
schema: isObject4(properties) ? field3(properties, key) : void 0,
|
|
13528
|
+
value: value[key]
|
|
13529
|
+
}))
|
|
13530
|
+
),
|
|
13531
|
+
header: []
|
|
13532
|
+
};
|
|
12592
13533
|
}
|
|
12593
|
-
if (kind === "application/octet-stream" || isFile(schema))
|
|
13534
|
+
if (kind === "application/octet-stream" || isFile(schema))
|
|
13535
|
+
return { body: { mode: "file", file: { src: "" } }, header: [] };
|
|
12594
13536
|
const language = /json|\*/.test(kind) ? "json" : kind.includes("xml") ? "xml" : kind.includes("html") ? "html" : kind.includes("javascript") ? "javascript" : "text";
|
|
12595
13537
|
const raw = typeof value === "string" ? value : language === "json" && value !== null ? JSON.stringify(value, null, 2) : "";
|
|
12596
|
-
if (!raw)
|
|
12597
|
-
|
|
13538
|
+
if (!raw)
|
|
13539
|
+
warnings.push({
|
|
13540
|
+
item,
|
|
13541
|
+
message: `Its body (${type.slice(0, 80)}) has no example in the file that the app can write out: the body is empty.`
|
|
13542
|
+
});
|
|
13543
|
+
return {
|
|
13544
|
+
body: { mode: "raw", raw, options: { raw: { language } } },
|
|
13545
|
+
header: kind.includes("*") || RAW_CONTENT_TYPES[language] === kind ? [] : [{ key: "Content-Type", value: type }]
|
|
13546
|
+
};
|
|
12598
13547
|
};
|
|
12599
13548
|
const components = field3(root, "components");
|
|
12600
13549
|
const schemes = swagger ? field3(root, "securityDefinitions") : isObject4(components) ? field3(components, "securitySchemes") : void 0;
|
|
@@ -12602,16 +13551,22 @@ function openApiCollection(root) {
|
|
|
12602
13551
|
if (!Array.isArray(requirements)) return {};
|
|
12603
13552
|
const names = Object.keys(requirements.find(isObject4) ?? {});
|
|
12604
13553
|
if (!names.length) return { auth: { type: "noauth" } };
|
|
12605
|
-
if (names.length > 1)
|
|
13554
|
+
if (names.length > 1)
|
|
13555
|
+
warnings.push({
|
|
13556
|
+
item,
|
|
13557
|
+
message: `It needs ${names.length} security schemes together (${named(names)}): only the first is set.`
|
|
13558
|
+
});
|
|
12606
13559
|
const scheme = resolve(isObject4(schemes) ? field3(schemes, names[0]) : void 0);
|
|
12607
13560
|
if (!isObject4(scheme)) return { auth: { type: `${names[0].slice(0, 60)}, which the file does not describe` } };
|
|
12608
13561
|
const type = text2(field3(scheme, "type"));
|
|
12609
13562
|
const how = text2(field3(scheme, "scheme")).toLowerCase();
|
|
12610
|
-
if (type === "basic" || type === "http" && how === "basic")
|
|
13563
|
+
if (type === "basic" || type === "http" && how === "basic")
|
|
13564
|
+
return { auth: { type: "basic", basic: { username: "{{authUsername}}", password: "{{authPassword}}" } } };
|
|
12611
13565
|
if (type === "http" && how === "bearer") return { auth: { type: "bearer", bearer: { token: "{{bearerToken}}" } } };
|
|
12612
13566
|
if (type === "apiKey") {
|
|
12613
13567
|
const place = text2(field3(scheme, "in"));
|
|
12614
|
-
if (place !== "header" && place !== "query")
|
|
13568
|
+
if (place !== "header" && place !== "query")
|
|
13569
|
+
return { auth: { type: `an API key in a ${place.slice(0, 20) || "place the file does not say"}` } };
|
|
12615
13570
|
return { auth: { type: "apikey", apikey: { key: text2(field3(scheme, "name")), value: "{{apiKey}}", in: place } } };
|
|
12616
13571
|
}
|
|
12617
13572
|
if (type === "oauth2") {
|
|
@@ -12621,7 +13576,15 @@ function openApiCollection(root) {
|
|
|
12621
13576
|
const client = flow("clientCredentials", "application");
|
|
12622
13577
|
const password = flow("password", "password");
|
|
12623
13578
|
const chosen = code ?? client ?? password;
|
|
12624
|
-
if (!isObject4(chosen))
|
|
13579
|
+
if (!isObject4(chosen))
|
|
13580
|
+
return {
|
|
13581
|
+
auth: {
|
|
13582
|
+
type,
|
|
13583
|
+
oauth2: {
|
|
13584
|
+
grant_type: (swagger ? text2(field3(scheme, "flow")) : Object.keys(isObject4(flows) ? flows : {})[0]) || "none said"
|
|
13585
|
+
}
|
|
13586
|
+
}
|
|
13587
|
+
};
|
|
12625
13588
|
const asked = field3(requirements.find(isObject4) ?? {}, names[0]);
|
|
12626
13589
|
const scopes = field3(chosen, "scopes");
|
|
12627
13590
|
const scope = (Array.isArray(asked) && asked.length ? asked.map(text2) : isObject4(scopes) ? Object.keys(scopes) : []).join(" ");
|
|
@@ -12654,14 +13617,24 @@ function openApiCollection(root) {
|
|
|
12654
13617
|
return withoutEndSlash(filled);
|
|
12655
13618
|
};
|
|
12656
13619
|
const host = text2(field3(root, "host"));
|
|
12657
|
-
const base = swagger ? withoutEndSlash(
|
|
13620
|
+
const base = swagger ? withoutEndSlash(
|
|
13621
|
+
`${host && `${text2(list3(field3(root, "schemes"))[0] ?? "https")}://${host}`}${text2(field3(root, "basePath"))}`
|
|
13622
|
+
) : server(field3(root, "servers")) ?? "";
|
|
12658
13623
|
if (!/^([a-z][a-z0-9+.-]*:\/\/|\{\{)/i.test(base)) {
|
|
12659
|
-
warnings.push({
|
|
13624
|
+
warnings.push({
|
|
13625
|
+
item: top,
|
|
13626
|
+
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.`
|
|
13627
|
+
});
|
|
12660
13628
|
}
|
|
12661
13629
|
const others = list3(field3(root, "servers")).length - 1;
|
|
12662
|
-
if (!swagger && others > 0)
|
|
13630
|
+
if (!swagger && others > 0)
|
|
13631
|
+
warnings.push({
|
|
13632
|
+
item: top,
|
|
13633
|
+
message: `The file names ${others + 1} servers: the collection's variable baseUrl holds the first (${base.slice(0, 80)}).`
|
|
13634
|
+
});
|
|
12663
13635
|
const folders = /* @__PURE__ */ new Map();
|
|
12664
|
-
for (const tag of list3(field3(root, "tags")).filter(isObject4))
|
|
13636
|
+
for (const tag of list3(field3(root, "tags")).filter(isObject4))
|
|
13637
|
+
folders.set(text2(field3(tag, "name")), { description: field3(tag, "description"), item: [] });
|
|
12665
13638
|
const loose = [];
|
|
12666
13639
|
const paths = field3(root, "paths");
|
|
12667
13640
|
for (const path of isObject4(paths) ? Object.keys(paths) : []) {
|
|
@@ -12674,7 +13647,8 @@ function openApiCollection(root) {
|
|
|
12674
13647
|
const requestName = text2(field3(operation, "summary")).trim() || text2(field3(operation, "operationId")).trim() || `${method.toUpperCase()} ${path}`;
|
|
12675
13648
|
item = `request "${requestName}"`;
|
|
12676
13649
|
const parameters = /* @__PURE__ */ new Map();
|
|
12677
|
-
for (const parameter of [...resolved(field3(pathItem, "parameters")), ...resolved(field3(operation, "parameters"))])
|
|
13650
|
+
for (const parameter of [...resolved(field3(pathItem, "parameters")), ...resolved(field3(operation, "parameters"))])
|
|
13651
|
+
parameters.set(`${text2(field3(parameter, "in"))}:${text2(field3(parameter, "name"))}`, parameter);
|
|
12678
13652
|
const query = [];
|
|
12679
13653
|
const header = [];
|
|
12680
13654
|
const cookies = [];
|
|
@@ -12691,7 +13665,12 @@ function openApiCollection(root) {
|
|
|
12691
13665
|
if (place === "formData") fields.push({ key, schema: parameter, value });
|
|
12692
13666
|
else if (place === "query") query.push({ key, value: said(value) });
|
|
12693
13667
|
else if (place === "cookie") cookies.push(`${key}=${said(value)}`);
|
|
12694
|
-
else if (place === "header" && !/^(accept|content-type|authorization)$/i.test(key))
|
|
13668
|
+
else if (place === "header" && !/^(accept|content-type|authorization)$/i.test(key))
|
|
13669
|
+
header.push({
|
|
13670
|
+
key,
|
|
13671
|
+
value: said(value),
|
|
13672
|
+
...field3(parameter, "required") !== true && said(value) === "" && { disabled: true }
|
|
13673
|
+
});
|
|
12695
13674
|
}
|
|
12696
13675
|
if (cookies.length) header.push({ key: "Cookie", value: cookies.join("; ") });
|
|
12697
13676
|
let body3;
|
|
@@ -12699,19 +13678,30 @@ function openApiCollection(root) {
|
|
|
12699
13678
|
const requestBody = resolve(field3(operation, "requestBody"));
|
|
12700
13679
|
const content = isObject4(requestBody) ? field3(requestBody, "content") : void 0;
|
|
12701
13680
|
if (fields.length) {
|
|
12702
|
-
body3 = form(
|
|
13681
|
+
body3 = form(
|
|
13682
|
+
consumes.some((type) => type.startsWith("multipart/")) || fields.some(({ schema }) => isFile(schema)) ? "multipart/form-data" : "application/x-www-form-urlencoded",
|
|
13683
|
+
fields
|
|
13684
|
+
);
|
|
12703
13685
|
} else if (inBody || isObject4(content)) {
|
|
12704
13686
|
const types = inBody ? consumes.length ? consumes : ["application/json"] : Object.keys(content).filter((type2) => isObject4(field3(content, type2)));
|
|
12705
13687
|
const type = types.find((entry) => /json/i.test(entry)) ?? types[0];
|
|
12706
13688
|
if (type !== void 0) {
|
|
12707
|
-
if (!inBody && types.length > 1)
|
|
13689
|
+
if (!inBody && types.length > 1)
|
|
13690
|
+
warnings.push({
|
|
13691
|
+
item,
|
|
13692
|
+
message: `Its body is described in ${types.length} media types (${named(types)}): the request has the ${type.slice(0, 80)} one.`
|
|
13693
|
+
});
|
|
12708
13694
|
const made2 = bodyOf2(type, inBody ?? field3(content, type));
|
|
12709
13695
|
body3 = made2.body;
|
|
12710
13696
|
header.push(...made2.header);
|
|
12711
13697
|
}
|
|
12712
13698
|
}
|
|
12713
13699
|
const tags = list3(field3(operation, "tags")).map(text2).filter(Boolean);
|
|
12714
|
-
if (tags.length > 1)
|
|
13700
|
+
if (tags.length > 1)
|
|
13701
|
+
warnings.push({
|
|
13702
|
+
item,
|
|
13703
|
+
message: `It has ${tags.length} tags (${named(tags)}): it is in the folder of the first.`
|
|
13704
|
+
});
|
|
12715
13705
|
const own3 = server(field3(operation, "servers")) ?? server(field3(pathItem, "servers"));
|
|
12716
13706
|
const request = {
|
|
12717
13707
|
name: requestName,
|
|
@@ -12719,7 +13709,11 @@ function openApiCollection(root) {
|
|
|
12719
13709
|
method: method.toUpperCase(),
|
|
12720
13710
|
description: field3(operation, "description"),
|
|
12721
13711
|
// A `{name}` of the path is the variable `{{name}}`, which the composer asks for before it sends.
|
|
12722
|
-
url: {
|
|
13712
|
+
url: {
|
|
13713
|
+
host: [own3 ?? "{{baseUrl}}"],
|
|
13714
|
+
path: path.replace(/^\//, "").split("/").map((part) => part.replace(/\{([^{}]+)\}/g, "{{$1}}")),
|
|
13715
|
+
query
|
|
13716
|
+
},
|
|
12723
13717
|
header,
|
|
12724
13718
|
...body3 && { body: body3 },
|
|
12725
13719
|
...auth3(field3(operation, "security"))
|
|
@@ -12731,48 +13725,50 @@ function openApiCollection(root) {
|
|
|
12731
13725
|
}
|
|
12732
13726
|
}
|
|
12733
13727
|
item = top;
|
|
12734
|
-
const made = {
|
|
13728
|
+
const made = {
|
|
13729
|
+
info: { name, description: isObject4(info) ? field3(info, "description") : void 0 },
|
|
13730
|
+
variable: [{ key: "baseUrl", value: base }],
|
|
13731
|
+
...auth3(field3(root, "security"))
|
|
13732
|
+
};
|
|
12735
13733
|
const tagged = [...folders].filter(([, folder]) => folder.item.length).map(([folderName, folder]) => ({ name: folderName, description: folder.description, item: folder.item }));
|
|
12736
|
-
if (!tagged.length && !loose.length)
|
|
13734
|
+
if (!tagged.length && !loose.length)
|
|
13735
|
+
throw new ImportError("This OpenAPI file describes no operation under `paths`, so there is no request to import.");
|
|
12737
13736
|
for (const [where, { outside, missing }] of unread) {
|
|
12738
|
-
if (outside.size)
|
|
12739
|
-
|
|
13737
|
+
if (outside.size)
|
|
13738
|
+
warnings.push({
|
|
13739
|
+
item: where,
|
|
13740
|
+
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.`
|
|
13741
|
+
});
|
|
13742
|
+
if (missing.size)
|
|
13743
|
+
warnings.push({
|
|
13744
|
+
item: where,
|
|
13745
|
+
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.`
|
|
13746
|
+
});
|
|
12740
13747
|
}
|
|
12741
13748
|
return { collection: { ...made, item: [...tagged, ...loose] }, warnings };
|
|
12742
13749
|
}
|
|
12743
13750
|
|
|
12744
|
-
// ../core/src/import/script-scan.ts
|
|
12745
|
-
var START = String.raw`(?<![\w$.])`;
|
|
12746
|
-
var escape = (text3) => text3.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
12747
|
-
var member = (name) => ({ api: name, finds: [[name, new RegExp(`${START}${escape(name)}(?![\\w$])`)]] });
|
|
12748
|
-
var required = (module) => [`require('${module}')`, new RegExp(`${START}require\\(\\s*["']${escape(module)}["']\\s*\\)`)];
|
|
12749
|
-
var called = (name) => [name, new RegExp(`${START}${escape(name)}\\s*\\(`)];
|
|
12750
|
-
var used = (name) => [name, new RegExp(`${START}${escape(name)}\\s*[.(]`)];
|
|
12751
|
-
var SCRIPT_SCAN = [
|
|
12752
|
-
...["pm.cookies.get", "pm.cookies.has", "pm.cookies.toObject", "pm.cookies.jar", "pm.visualizer.set", "pm.vault.get", "pm.require", "postman.getResponseCookie"].map(member),
|
|
12753
|
-
{ api: "legacy library globals (_, CryptoJS, tv4, cheerio, xml2Json, Backbone)", finds: ["tv4", "cheerio", "xml2Json", "Backbone"].map(used) },
|
|
12754
|
-
{ api: "clearTimeout, setInterval, clearInterval", finds: ["clearTimeout", "setInterval", "clearInterval"].map(called) },
|
|
12755
|
-
...["ajv", "cheerio", "csv-parse/lib/sync", "postman-collection", "tv4", "xml2js"].map((module) => ({ api: `require('${module}')`, finds: [required(module)] })),
|
|
12756
|
-
{
|
|
12757
|
-
api: "require of a Node module (buffer, events, path, querystring, stream, string_decoder, timers, url, util)",
|
|
12758
|
-
finds: ["buffer", "events", "path", "querystring", "stream", "string_decoder", "timers", "url", "util"].map(required)
|
|
12759
|
-
}
|
|
12760
|
-
];
|
|
12761
|
-
function unsupportedApis(script) {
|
|
12762
|
-
return SCRIPT_SCAN.flatMap((entry) => entry.finds).filter(([, pattern]) => pattern.test(script)).map(([label]) => label);
|
|
12763
|
-
}
|
|
12764
|
-
|
|
12765
13751
|
// ../core/src/import/yaml.ts
|
|
12766
13752
|
var import_yaml = __toESM(require_dist(), 1);
|
|
12767
13753
|
var MAX_YAML_ALIASES = 100;
|
|
12768
13754
|
var MAX_YAML_CHARS = 1024 * 1024;
|
|
12769
13755
|
var YAML_ALIASES = `The YAML file repeats parts of itself through aliases ${MAX_YAML_ALIASES} times or more and is refused: a file written that way grows without end as it is read.`;
|
|
12770
|
-
var YAML_TOO_LARGE = "
|
|
13756
|
+
var YAML_TOO_LARGE = "This file is not JSON, and a YAML file can be 1 MB at most: reading YAML takes far more memory than JSON. Convert it to JSON and import that.";
|
|
12771
13757
|
var NOT_READABLE = "The file is neither JSON nor YAML that can be read, so it is not a file Wayfarer can import.";
|
|
12772
13758
|
function parseYaml(text3) {
|
|
12773
13759
|
if (text3.length > MAX_YAML_CHARS) throw new ImportError(YAML_TOO_LARGE);
|
|
12774
13760
|
try {
|
|
12775
|
-
return
|
|
13761
|
+
return JSON.parse(
|
|
13762
|
+
JSON.stringify(
|
|
13763
|
+
(0, import_yaml.parse)(text3, {
|
|
13764
|
+
maxAliasCount: MAX_YAML_ALIASES,
|
|
13765
|
+
schema: "core",
|
|
13766
|
+
logLevel: "error",
|
|
13767
|
+
uniqueKeys: false,
|
|
13768
|
+
prettyErrors: false
|
|
13769
|
+
})
|
|
13770
|
+
)
|
|
13771
|
+
);
|
|
12776
13772
|
} catch (error) {
|
|
12777
13773
|
throw new ImportError(error instanceof Error && /alias count/i.test(error.message) ? YAML_ALIASES : NOT_READABLE);
|
|
12778
13774
|
}
|
|
@@ -12812,7 +13808,12 @@ function scriptWarnings(item, scripts2) {
|
|
|
12812
13808
|
const found = unsupportedApis(scripts2[kind]);
|
|
12813
13809
|
if (!found.length) return [];
|
|
12814
13810
|
const names = found.length > 1 ? `${found.slice(0, -1).join(", ")} and ${found.at(-1)}` : found[0];
|
|
12815
|
-
return [
|
|
13811
|
+
return [
|
|
13812
|
+
{
|
|
13813
|
+
item,
|
|
13814
|
+
message: `Its ${kind === "pre" ? "pre-request" : "post-response"} script uses ${names}, which Wayfarer does not have, so the script stops with an error there.`
|
|
13815
|
+
}
|
|
13816
|
+
];
|
|
12816
13817
|
});
|
|
12817
13818
|
}
|
|
12818
13819
|
function inheritedScriptWarnings(payload) {
|
|
@@ -12827,9 +13828,13 @@ function curlToRequest(text3) {
|
|
|
12827
13828
|
const parsed2 = parseCurl(text3);
|
|
12828
13829
|
const { content, warnings } = curlRequest(parsed2);
|
|
12829
13830
|
const issues = validateRequestContent(content, "request");
|
|
12830
|
-
if (issues.length) throw new ImportError("
|
|
13831
|
+
if (issues.length) throw new ImportError("This cURL command does not describe a request Wayfarer can send.", issues);
|
|
12831
13832
|
const address2 = URL.parse(parsed2.request.url);
|
|
12832
|
-
return {
|
|
13833
|
+
return {
|
|
13834
|
+
name: `${content.method} ${address2 ? address2.host + address2.pathname : parsed2.request.url}`,
|
|
13835
|
+
content,
|
|
13836
|
+
warnings
|
|
13837
|
+
};
|
|
12833
13838
|
}
|
|
12834
13839
|
function built(format, name, requests) {
|
|
12835
13840
|
const now = Date.now();
|
|
@@ -12838,7 +13843,15 @@ function built(format, name, requests) {
|
|
|
12838
13843
|
const payload = {
|
|
12839
13844
|
$id: COLLECTION_FORMAT,
|
|
12840
13845
|
meta: top,
|
|
12841
|
-
collection: {
|
|
13846
|
+
collection: {
|
|
13847
|
+
id: top.id,
|
|
13848
|
+
meta: top,
|
|
13849
|
+
name,
|
|
13850
|
+
order: 0,
|
|
13851
|
+
variables: [],
|
|
13852
|
+
auth: { type: "none" },
|
|
13853
|
+
scripts: { pre: "", post: "" }
|
|
13854
|
+
},
|
|
12842
13855
|
folders: [],
|
|
12843
13856
|
requests: requests.map(({ name: requestName, content }, order) => {
|
|
12844
13857
|
const own3 = meta();
|
|
@@ -12850,7 +13863,9 @@ function built(format, name, requests) {
|
|
|
12850
13863
|
format,
|
|
12851
13864
|
formatName: IMPORT_FORMATS[format],
|
|
12852
13865
|
counts: { collections: 1, folders: 0, requests: requests.length, environments: 0 },
|
|
12853
|
-
warnings: requests.flatMap(
|
|
13866
|
+
warnings: requests.flatMap(
|
|
13867
|
+
({ name: requestName, warnings }) => warnings.map((message) => ({ item: `request "${requestName}"`, message }))
|
|
13868
|
+
)
|
|
12854
13869
|
},
|
|
12855
13870
|
collections: [added(payload)],
|
|
12856
13871
|
environments: []
|
|
@@ -12858,10 +13873,63 @@ function built(format, name, requests) {
|
|
|
12858
13873
|
}
|
|
12859
13874
|
function added(payload) {
|
|
12860
13875
|
const { payload: checked, plan, errors } = importCollection(payload, { duplicateAsNew: true });
|
|
12861
|
-
if (!checked || !plan) throw new ImportError(
|
|
13876
|
+
if (!checked || !plan) throw new ImportError("This file holds no request Wayfarer can read.", errors ?? []);
|
|
12862
13877
|
return { payload: checked, plan };
|
|
12863
13878
|
}
|
|
12864
13879
|
function importText(text3, options = {}) {
|
|
13880
|
+
let imported;
|
|
13881
|
+
try {
|
|
13882
|
+
imported = readText(text3, options);
|
|
13883
|
+
} catch (error) {
|
|
13884
|
+
if (error instanceof RangeError) throw new ImportError("The file is nested too deeply to read.");
|
|
13885
|
+
throw error;
|
|
13886
|
+
}
|
|
13887
|
+
return {
|
|
13888
|
+
...imported,
|
|
13889
|
+
report: {
|
|
13890
|
+
...imported.report,
|
|
13891
|
+
warnings: [...imported.report.warnings, ...unsetVariableWarnings(imported.collections, imported.environments)]
|
|
13892
|
+
}
|
|
13893
|
+
};
|
|
13894
|
+
}
|
|
13895
|
+
var PLAIN_VARIABLE = /^[\w.-]+$/;
|
|
13896
|
+
var PLACEHOLDERS = /{{([^{}]*)}}/g;
|
|
13897
|
+
var SCRIPT_SETS = /\.set\(\s*["'`]([^"'`]+)["'`]/g;
|
|
13898
|
+
function unsetVariableWarnings(collections, environments) {
|
|
13899
|
+
return collections.flatMap(({ payload }) => {
|
|
13900
|
+
const set = /* @__PURE__ */ new Set([
|
|
13901
|
+
...[payload.collection, ...payload.folders].flatMap((holder) => holder.variables.map((row) => row.key)),
|
|
13902
|
+
...environments.flatMap((environment2) => environment2.vars.map((row) => row.key))
|
|
13903
|
+
]);
|
|
13904
|
+
const scripts2 = [payload.collection, ...payload.folders, ...payload.requests].flatMap(({ scripts: scripts3 }) => [
|
|
13905
|
+
scripts3.pre,
|
|
13906
|
+
scripts3.post
|
|
13907
|
+
]);
|
|
13908
|
+
for (const script of scripts2) for (const [, name] of script.matchAll(SCRIPT_SETS)) set.add(name);
|
|
13909
|
+
const uses = payload.requests.map(({ url, headers: headers2, body: body3, auth: auth3 }) => {
|
|
13910
|
+
const text3 = JSON.stringify({ url, headers: headers2, body: body3, auth: auth3 });
|
|
13911
|
+
return new Set(
|
|
13912
|
+
[...text3.matchAll(PLACEHOLDERS)].map(([, name]) => name.trim()).filter((name) => PLAIN_VARIABLE.test(name))
|
|
13913
|
+
);
|
|
13914
|
+
});
|
|
13915
|
+
const unset = [...new Set(uses.flatMap((names2) => [...names2]))].filter((name) => !set.has(name)).sort();
|
|
13916
|
+
if (!unset.length) return [];
|
|
13917
|
+
const using = uses.filter((names2) => unset.some((name) => names2.has(name))).length;
|
|
13918
|
+
const total = payload.requests.length;
|
|
13919
|
+
const listed = unset.slice(0, unset.length > 5 ? 5 : unset.length).map((name) => `{{${name}}}`);
|
|
13920
|
+
if (unset.length > 5) listed.push(`${unset.length - 5} more`);
|
|
13921
|
+
const names = listed.length < 2 ? listed[0] : `${listed.slice(0, -1).join(", ")} and ${listed.at(-1)}`;
|
|
13922
|
+
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"}`;
|
|
13923
|
+
const it = unset.length === 1 ? "it" : "them";
|
|
13924
|
+
return [
|
|
13925
|
+
{
|
|
13926
|
+
item: `collection "${payload.collection.name}"`,
|
|
13927
|
+
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.`
|
|
13928
|
+
}
|
|
13929
|
+
];
|
|
13930
|
+
});
|
|
13931
|
+
}
|
|
13932
|
+
function readText(text3, options) {
|
|
12865
13933
|
if (isOversizedImport(text3)) throw new ImportError(IMPORT_TOO_LARGE);
|
|
12866
13934
|
if (text3.startsWith("\uFEFF")) text3 = text3.slice(1);
|
|
12867
13935
|
if (isCurlCommand(text3)) {
|
|
@@ -12873,8 +13941,12 @@ function importText(text3, options = {}) {
|
|
|
12873
13941
|
if (!json.ok && (typeof value !== "object" || value === null)) throw new ImportError(NOT_READABLE);
|
|
12874
13942
|
const format = detectFormat(value);
|
|
12875
13943
|
if (!format) {
|
|
12876
|
-
if (of(idOf(value), "wayfarer/workspace"))
|
|
12877
|
-
|
|
13944
|
+
if (of(idOf(value), "wayfarer/workspace"))
|
|
13945
|
+
throw new ImportError("This is a workspace backup, which replaces everything. Restore it in Settings.");
|
|
13946
|
+
if (isPostmanV1(value))
|
|
13947
|
+
throw new ImportError(
|
|
13948
|
+
"This is a Postman Collection v1 file, which Wayfarer does not read. In Postman, export the collection as Collection v2.1 and import that."
|
|
13949
|
+
);
|
|
12878
13950
|
throw new ImportError(UNKNOWN);
|
|
12879
13951
|
}
|
|
12880
13952
|
const report = (counts2, warnings) => ({
|
|
@@ -12884,7 +13956,10 @@ function importText(text3, options = {}) {
|
|
|
12884
13956
|
warnings
|
|
12885
13957
|
});
|
|
12886
13958
|
if (format === "har") {
|
|
12887
|
-
const requests = parseHar(value).map(({ name, request, warnings }) => ({
|
|
13959
|
+
const requests = parseHar(value).map(({ name, request, warnings }) => ({
|
|
13960
|
+
name,
|
|
13961
|
+
...curlRequest({ request, warnings })
|
|
13962
|
+
}));
|
|
12888
13963
|
return built("har", "Imported from HAR", requests);
|
|
12889
13964
|
}
|
|
12890
13965
|
if (format === "postman-collection" || format === "openapi") {
|
|
@@ -12892,7 +13967,11 @@ function importText(text3, options = {}) {
|
|
|
12892
13967
|
const { payload: payload2, warnings } = postmanCollection(source.collection);
|
|
12893
13968
|
const collection = added(payload2);
|
|
12894
13969
|
return {
|
|
12895
|
-
report: report({ collections: 1, folders: payload2.folders.length, requests: payload2.requests.length }, [
|
|
13970
|
+
report: report({ collections: 1, folders: payload2.folders.length, requests: payload2.requests.length }, [
|
|
13971
|
+
...source.warnings,
|
|
13972
|
+
...warnings,
|
|
13973
|
+
...inheritedScriptWarnings(collection.payload)
|
|
13974
|
+
]),
|
|
12896
13975
|
collections: [collection],
|
|
12897
13976
|
environments: []
|
|
12898
13977
|
};
|
|
@@ -12905,10 +13984,15 @@ function importText(text3, options = {}) {
|
|
|
12905
13984
|
if (issues.length) throw new ImportError("The file's environments are not ones the app can hold.", issues);
|
|
12906
13985
|
const count = (of2) => collections.reduce((sum, { payload: payload2 }) => sum + of2(payload2).length, 0);
|
|
12907
13986
|
return {
|
|
12908
|
-
report: report(
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
13987
|
+
report: report(
|
|
13988
|
+
{
|
|
13989
|
+
collections: collections.length,
|
|
13990
|
+
folders: count((payload2) => payload2.folders),
|
|
13991
|
+
requests: count((payload2) => payload2.requests),
|
|
13992
|
+
environments: environments.length
|
|
13993
|
+
},
|
|
13994
|
+
[...warnings, ...read.flatMap((collection) => collection.warnings)]
|
|
13995
|
+
),
|
|
12912
13996
|
collections,
|
|
12913
13997
|
environments
|
|
12914
13998
|
};
|
|
@@ -12921,12 +14005,28 @@ function importText(text3, options = {}) {
|
|
|
12921
14005
|
}
|
|
12922
14006
|
if (format === "wayfarer-environments") {
|
|
12923
14007
|
const result = validateEnvironmentExport(value);
|
|
12924
|
-
if (!result.payload)
|
|
12925
|
-
|
|
14008
|
+
if (!result.payload)
|
|
14009
|
+
throw new ImportError(
|
|
14010
|
+
"The file is not a valid Wayfarer environments file.",
|
|
14011
|
+
(result.errors ?? []).map((message) => ({ path: "", message }))
|
|
14012
|
+
);
|
|
14013
|
+
return {
|
|
14014
|
+
report: report({ environments: result.payload.length }, []),
|
|
14015
|
+
collections: [],
|
|
14016
|
+
environments: result.payload
|
|
14017
|
+
};
|
|
12926
14018
|
}
|
|
12927
14019
|
const { payload, plan, errors } = importCollection(value, options);
|
|
12928
|
-
if (!payload || !plan)
|
|
12929
|
-
|
|
14020
|
+
if (!payload || !plan)
|
|
14021
|
+
throw new ImportError("This file is not a Wayfarer collection. What does not match is listed below.", errors ?? []);
|
|
14022
|
+
return {
|
|
14023
|
+
report: report(
|
|
14024
|
+
{ collections: 1, folders: payload.folders.length, requests: payload.requests.length },
|
|
14025
|
+
inheritedScriptWarnings(payload)
|
|
14026
|
+
),
|
|
14027
|
+
collections: [{ payload, plan }],
|
|
14028
|
+
environments: []
|
|
14029
|
+
};
|
|
12930
14030
|
}
|
|
12931
14031
|
|
|
12932
14032
|
// ../core/src/run/report.ts
|
|
@@ -12939,10 +14039,21 @@ var counts = (results) => ({
|
|
|
12939
14039
|
errors: results.filter((result) => result.error !== void 0).length
|
|
12940
14040
|
});
|
|
12941
14041
|
function runReportJson(report, info) {
|
|
12942
|
-
return {
|
|
14042
|
+
return {
|
|
14043
|
+
$id: RUN_REPORT_FORMAT,
|
|
14044
|
+
...info,
|
|
14045
|
+
iterations: report.iterations,
|
|
14046
|
+
ended: report.ended,
|
|
14047
|
+
...report.reason !== void 0 && { reason: report.reason },
|
|
14048
|
+
totals: counts(report.results),
|
|
14049
|
+
results: report.results
|
|
14050
|
+
};
|
|
12943
14051
|
}
|
|
12944
14052
|
function xml(text3) {
|
|
12945
|
-
return text3.replace(/[^\t\n\r --�\u{10000}-\u{10FFFF}]/gu, "\uFFFD").replace(
|
|
14053
|
+
return text3.replace(/[^\t\n\r --�\u{10000}-\u{10FFFF}]/gu, "\uFFFD").replace(
|
|
14054
|
+
/[&<>"\t\n\r]/g,
|
|
14055
|
+
(char) => ({ "&": "&", "<": "<", ">": ">", '"': """, " ": "	", "\n": " ", "\r": " " })[char] ?? char
|
|
14056
|
+
);
|
|
12946
14057
|
}
|
|
12947
14058
|
var seconds = (ms) => ((ms ?? 0) / 1e3).toFixed(3);
|
|
12948
14059
|
function junitXml(report, info) {
|
|
@@ -12953,12 +14064,18 @@ function junitXml(report, info) {
|
|
|
12953
14064
|
<failure type="AssertionFailure" message="${xml(test.error ?? "")}"/>
|
|
12954
14065
|
</testcase>`
|
|
12955
14066
|
);
|
|
12956
|
-
if (result.error !== void 0)
|
|
14067
|
+
if (result.error !== void 0)
|
|
14068
|
+
cases.push(
|
|
14069
|
+
` <testcase name="${classname}" classname="${classname}">
|
|
12957
14070
|
<error type="Error" message="${xml(result.error)}"/>
|
|
12958
|
-
</testcase>`
|
|
12959
|
-
|
|
14071
|
+
</testcase>`
|
|
14072
|
+
);
|
|
14073
|
+
if (result.skipped)
|
|
14074
|
+
cases.push(
|
|
14075
|
+
` <testcase name="${classname}" classname="${classname}">
|
|
12960
14076
|
<skipped message="pm.execution.skipRequest()"/>
|
|
12961
|
-
</testcase>`
|
|
14077
|
+
</testcase>`
|
|
14078
|
+
);
|
|
12962
14079
|
const totals2 = counts([result]);
|
|
12963
14080
|
const name = xml(report.iterations > 1 ? `${result.name} (iteration ${result.iteration + 1})` : result.name);
|
|
12964
14081
|
const open = ` <testsuite name="${name}" id="${xml(result.id)}" tests="${cases.length}" failures="${totals2.failedTests}" errors="${totals2.errors}" skipped="${totals2.skipped}" time="${seconds(result.durationMs)}"`;
|
|
@@ -12976,6 +14093,97 @@ ${cases.join("\n")}
|
|
|
12976
14093
|
""
|
|
12977
14094
|
].join("\n");
|
|
12978
14095
|
}
|
|
14096
|
+
var REPORT_COLOURS = {
|
|
14097
|
+
light: {
|
|
14098
|
+
canvas: "#FBFAF8",
|
|
14099
|
+
panel: "#F6F5F2",
|
|
14100
|
+
primary: "#26231E",
|
|
14101
|
+
secondary: "#55524D",
|
|
14102
|
+
tertiary: "#726F69",
|
|
14103
|
+
line: "#DEDAD3",
|
|
14104
|
+
error: "#A51F1E"
|
|
14105
|
+
},
|
|
14106
|
+
dark: {
|
|
14107
|
+
canvas: "#161513",
|
|
14108
|
+
panel: "#22211E",
|
|
14109
|
+
primary: "#EBE9E4",
|
|
14110
|
+
secondary: "#C0BDB6",
|
|
14111
|
+
tertiary: "#9A9790",
|
|
14112
|
+
line: "#423F39",
|
|
14113
|
+
error: "#F7958A"
|
|
14114
|
+
}
|
|
14115
|
+
};
|
|
14116
|
+
function html(text3) {
|
|
14117
|
+
return text3.replace(
|
|
14118
|
+
/[&<>"']/g,
|
|
14119
|
+
(char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char] ?? char
|
|
14120
|
+
);
|
|
14121
|
+
}
|
|
14122
|
+
var palette = (colours) => Object.entries(colours).map(([name, value]) => `--${name}: ${value};`).join(" ");
|
|
14123
|
+
function runReportHtml(report, info) {
|
|
14124
|
+
const shared = sharedErrors(report.results);
|
|
14125
|
+
const sharedNote = (error) => {
|
|
14126
|
+
const index = shared.findIndex((entry) => entry.error === error);
|
|
14127
|
+
return index < 0 ? error : shared.length === 1 ? "Same error as above" : `Same error as ${index + 1} above`;
|
|
14128
|
+
};
|
|
14129
|
+
const rows2 = report.results.map((result, index) => {
|
|
14130
|
+
const failed = result.tests.filter((test) => !test.passed);
|
|
14131
|
+
const problems = [
|
|
14132
|
+
...result.error === void 0 ? [] : [sharedNote(result.error)],
|
|
14133
|
+
...failed.map((test) => test.error ? `${test.name}: ${test.error}` : test.name)
|
|
14134
|
+
];
|
|
14135
|
+
const status = result.skipped ? "Skipped" : result.status ? `${result.status} ${result.statusText ?? ""}`.trim() : "No response";
|
|
14136
|
+
return `<tr${runFailed(result) ? ' class="failed"' : ""}>
|
|
14137
|
+
<td>${index + 1}</td><td>${result.iteration + 1}</td>
|
|
14138
|
+
<td>${html(result.name)}${problems.map((problem) => `<p class="problem">${html(problem)}</p>`).join("")}</td>
|
|
14139
|
+
<td>${html(status)}</td><td>${result.durationMs === void 0 ? "\u2014" : `${result.durationMs} ms`}</td>
|
|
14140
|
+
<td>${result.tests.length ? `${result.tests.length - failed.length} of ${result.tests.length} passed` : "\u2014"}</td>
|
|
14141
|
+
</tr>`;
|
|
14142
|
+
}).join("\n");
|
|
14143
|
+
const sharedBlocks = shared.map(
|
|
14144
|
+
({ error, count }, index) => `<p class="shared">${shared.length > 1 ? `<b>${index + 1}.</b> ` : ""}<b>${html(sharedErrorHeading(count))}</b> ${html(error)}</p>`
|
|
14145
|
+
).join("\n");
|
|
14146
|
+
return `<!doctype html>
|
|
14147
|
+
<html lang="en-GB">
|
|
14148
|
+
<head>
|
|
14149
|
+
<meta charset="utf-8">
|
|
14150
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
14151
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'">
|
|
14152
|
+
<title>Run ${html(info.name)}</title>
|
|
14153
|
+
<style>
|
|
14154
|
+
:root { color-scheme: light dark; ${palette(REPORT_COLOURS.light)} }
|
|
14155
|
+
@media (prefers-color-scheme: dark) { :root { ${palette(REPORT_COLOURS.dark)} } }
|
|
14156
|
+
body { margin: 0; padding: 24px 16px; background: var(--canvas); color: var(--primary);
|
|
14157
|
+
font: 14px/1.5 Figtree, system-ui, sans-serif; }
|
|
14158
|
+
main { max-width: 1100px; margin: 0 auto; }
|
|
14159
|
+
h1 { margin: 0 0 4px; font-size: 1.375rem; font-weight: 600; }
|
|
14160
|
+
.meta, .counts { margin: 0 0 16px; color: var(--secondary); }
|
|
14161
|
+
.shared { margin: 0 0 12px; padding: 10px 14px; border: 1px solid var(--error); border-radius: 8px; color: var(--error); }
|
|
14162
|
+
table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }
|
|
14163
|
+
th { text-align: start; font-weight: 500; color: var(--tertiary); }
|
|
14164
|
+
th, td { padding: 8px 10px; border-bottom: 1px solid var(--line); vertical-align: top; overflow-wrap: anywhere; }
|
|
14165
|
+
td:nth-child(4), td:nth-child(5) { font-family: "JetBrains Mono", ui-monospace, monospace; }
|
|
14166
|
+
.problem { margin: 4px 0 0; color: var(--error); font-size: 0.75rem; }
|
|
14167
|
+
tr.failed td:first-child { box-shadow: inset 2px 0 0 var(--error); }
|
|
14168
|
+
</style>
|
|
14169
|
+
</head>
|
|
14170
|
+
<body>
|
|
14171
|
+
<main>
|
|
14172
|
+
<h1>Run ${html(info.name)}</h1>
|
|
14173
|
+
<p class="meta">Started ${html(info.startedAt)}</p>
|
|
14174
|
+
<p class="counts">${html(runEnding(report))} ${html(runCounts(report))}</p>
|
|
14175
|
+
${sharedBlocks}
|
|
14176
|
+
<table>
|
|
14177
|
+
<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>
|
|
14178
|
+
<tbody>
|
|
14179
|
+
${rows2}
|
|
14180
|
+
</tbody>
|
|
14181
|
+
</table>
|
|
14182
|
+
</main>
|
|
14183
|
+
</body>
|
|
14184
|
+
</html>
|
|
14185
|
+
`;
|
|
14186
|
+
}
|
|
12979
14187
|
|
|
12980
14188
|
// src/run.ts
|
|
12981
14189
|
import variant from "@jitl/quickjs-wasmfile-release-sync";
|
|
@@ -13020,10 +14228,16 @@ async function runCollection(input, options) {
|
|
|
13020
14228
|
const transport = new FetchTransport();
|
|
13021
14229
|
const ports = {
|
|
13022
14230
|
runScript: async (script, env, response, { send: send2, ...extras }) => {
|
|
13023
|
-
const result = await runScript(
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
14231
|
+
const result = await runScript(
|
|
14232
|
+
await engine(),
|
|
14233
|
+
script,
|
|
14234
|
+
{ ...extras, environment: env, ...response && { response: scriptResponse(response) } },
|
|
14235
|
+
SCRIPT_LIMITS,
|
|
14236
|
+
{
|
|
14237
|
+
libraries: await loadLibraries(script, loadLibrary, libraries),
|
|
14238
|
+
...send2 && { send: send2 }
|
|
14239
|
+
}
|
|
14240
|
+
);
|
|
13027
14241
|
if (result.limit) engine = engines();
|
|
13028
14242
|
return result;
|
|
13029
14243
|
},
|
|
@@ -13038,7 +14252,9 @@ async function runCollection(input, options) {
|
|
|
13038
14252
|
environment: () => environment2 && {
|
|
13039
14253
|
name: environment2.name,
|
|
13040
14254
|
vars: environment2.vars,
|
|
13041
|
-
change: (changes) => Promise.resolve(
|
|
14255
|
+
change: (changes) => Promise.resolve(
|
|
14256
|
+
environment2 && (environment2 = { ...environment2, vars: applyVariableChanges(environment2.vars, changes) })
|
|
14257
|
+
)
|
|
13042
14258
|
},
|
|
13043
14259
|
globals: () => globals,
|
|
13044
14260
|
changeGlobals: (changes) => Promise.resolve(globals = applyVariableChanges(globals, changes)),
|
|
@@ -13060,7 +14276,13 @@ async function runCollection(input, options) {
|
|
|
13060
14276
|
const send = async (doc, iteration, signal) => {
|
|
13061
14277
|
const ancestors = ancestorsOf(source.collection, source.folders, doc.folderId);
|
|
13062
14278
|
const folder = folderChain(source.folders, doc.folderId).flatMap((entry) => entry.variables);
|
|
13063
|
-
const scopes = () => ({
|
|
14279
|
+
const scopes = () => ({
|
|
14280
|
+
environment: environment2?.vars,
|
|
14281
|
+
folder,
|
|
14282
|
+
collection: collectionVariables,
|
|
14283
|
+
global: globals,
|
|
14284
|
+
data: iteration.data
|
|
14285
|
+
});
|
|
13064
14286
|
const { outcome } = await runOutcome(
|
|
13065
14287
|
() => exchange(
|
|
13066
14288
|
{
|
|
@@ -13073,7 +14295,10 @@ async function runCollection(input, options) {
|
|
|
13073
14295
|
scopes,
|
|
13074
14296
|
ancestors,
|
|
13075
14297
|
readSecrets: async (ids) => {
|
|
13076
|
-
if (!vault)
|
|
14298
|
+
if (!vault)
|
|
14299
|
+
throw new SendBlockedError(
|
|
14300
|
+
"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."
|
|
14301
|
+
);
|
|
13077
14302
|
const found = /* @__PURE__ */ new Map();
|
|
13078
14303
|
for (const id of ids) {
|
|
13079
14304
|
const value = await readSecret(id);
|
|
@@ -13081,12 +14306,27 @@ async function runCollection(input, options) {
|
|
|
13081
14306
|
}
|
|
13082
14307
|
return found;
|
|
13083
14308
|
},
|
|
13084
|
-
readFile: (ref) => Promise.reject(
|
|
14309
|
+
readFile: (ref) => Promise.reject(
|
|
14310
|
+
new SendBlockedError(
|
|
14311
|
+
`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.`
|
|
14312
|
+
)
|
|
14313
|
+
),
|
|
13085
14314
|
blockUnresolved: true,
|
|
13086
|
-
composed: {
|
|
14315
|
+
composed: {
|
|
14316
|
+
url: doc.url,
|
|
14317
|
+
scopes: { ...baseline, folder, data: iteration.data },
|
|
14318
|
+
accepted: acceptedHosts
|
|
14319
|
+
}
|
|
13087
14320
|
}),
|
|
13088
|
-
info: {
|
|
13089
|
-
|
|
14321
|
+
info: {
|
|
14322
|
+
requestName: doc.name,
|
|
14323
|
+
requestId: doc.id,
|
|
14324
|
+
path: [source.collection.name, ...folderChain(source.folders, doc.folderId).map((entry) => entry.name)]
|
|
14325
|
+
},
|
|
14326
|
+
collection: {
|
|
14327
|
+
variables: () => collectionVariables,
|
|
14328
|
+
change: (changes) => Promise.resolve(collectionVariables = applyVariableChanges(collectionVariables, changes))
|
|
14329
|
+
},
|
|
13090
14330
|
inherited: ancestors,
|
|
13091
14331
|
folderVariables: () => folder,
|
|
13092
14332
|
signal,
|
|
@@ -13123,17 +14363,21 @@ Options:
|
|
|
13123
14363
|
-e, --environment <file> The variables: a Wayfarer environments file or a
|
|
13124
14364
|
Postman environment. A workspace backup brings
|
|
13125
14365
|
its active environment unless one is given.
|
|
14366
|
+
--environment-name <name>
|
|
14367
|
+
Which environment of a file that holds several.
|
|
13126
14368
|
-d, --data <file> A data file, CSV or JSON: one iteration per row.
|
|
13127
14369
|
-n, --iterations <n> How many iterations (default: one per row of the
|
|
13128
14370
|
data file, or 1).
|
|
13129
14371
|
--collection <name> Which collection of a file that holds several.
|
|
13130
|
-
--reporter <list> cli, json, junit, comma-separated (default
|
|
13131
|
-
json writes wayfarer-run.json
|
|
13132
|
-
wayfarer-run.junit.xml
|
|
14372
|
+
--reporter <list> cli, json, junit, html, comma-separated (default
|
|
14373
|
+
cli). json writes wayfarer-run.json, junit
|
|
14374
|
+
wayfarer-run.junit.xml and html wayfarer-run.html,
|
|
14375
|
+
in --out.
|
|
13133
14376
|
--out <dir> Where the reports are written (default: here).
|
|
13134
14377
|
--bail Stop at the first failure.
|
|
13135
|
-
--timeout <ms> Give up on a request after this long
|
|
13136
|
-
|
|
14378
|
+
--timeout <ms> Give up on a request after this long, at most
|
|
14379
|
+
300000 (default 300000: Node's fetch waits no
|
|
14380
|
+
longer for a response).
|
|
13137
14381
|
--insecure Do not verify servers' TLS certificates.
|
|
13138
14382
|
--allow-scripts Run the file's scripts. A file's scripts are not
|
|
13139
14383
|
reviewed, so a collection that has any is not run
|
|
@@ -13144,53 +14388,74 @@ Options:
|
|
|
13144
14388
|
-v, --version
|
|
13145
14389
|
-h, --help
|
|
13146
14390
|
|
|
13147
|
-
Exit codes: 0 every request answered and every test passed; 1 a test failed
|
|
13148
|
-
|
|
14391
|
+
Exit codes: 0 every request answered and every test passed; 1 a test failed,
|
|
14392
|
+
a request got no response, or a report could not be written; 2 nothing was
|
|
14393
|
+
run (see the message).`;
|
|
14394
|
+
var NODE_FETCH_TIMEOUT_MS = 3e5;
|
|
13149
14395
|
var Refusal = class extends Error {
|
|
13150
14396
|
};
|
|
13151
|
-
async function
|
|
14397
|
+
async function readText2(file, what) {
|
|
13152
14398
|
try {
|
|
13153
14399
|
return await readFile(file, "utf8");
|
|
13154
14400
|
} catch (error) {
|
|
13155
|
-
throw new Refusal(
|
|
14401
|
+
throw new Refusal(
|
|
14402
|
+
`The ${what} ${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
14403
|
+
);
|
|
13156
14404
|
}
|
|
13157
14405
|
}
|
|
13158
|
-
function pick(list4, name, what, file) {
|
|
14406
|
+
function pick(list4, name, what, flag2, file) {
|
|
13159
14407
|
if (name !== void 0) {
|
|
13160
14408
|
const found = list4.find((entry) => entry.name === name);
|
|
13161
|
-
if (!found)
|
|
14409
|
+
if (!found)
|
|
14410
|
+
throw new Refusal(
|
|
14411
|
+
`${file} has no ${what} named "${name}". It has: ${list4.map((entry) => `"${entry.name}"`).join(", ")}.`
|
|
14412
|
+
);
|
|
13162
14413
|
return found;
|
|
13163
14414
|
}
|
|
13164
|
-
if (list4.length > 1)
|
|
14415
|
+
if (list4.length > 1)
|
|
14416
|
+
throw new Refusal(
|
|
14417
|
+
`${file} holds ${list4.length} ${what}s: name one with --${flag2} (${list4.map((entry) => `"${entry.name}"`).join(", ")}).`
|
|
14418
|
+
);
|
|
13165
14419
|
const [only] = list4;
|
|
13166
14420
|
if (!only) throw new Refusal(`${file} holds no ${what}.`);
|
|
13167
14421
|
return only;
|
|
13168
14422
|
}
|
|
13169
14423
|
async function openVault(record, secrets, file) {
|
|
13170
14424
|
const passphrase = process.env["WAYFARER_VAULT_PASSPHRASE"];
|
|
13171
|
-
if (passphrase === void 0)
|
|
14425
|
+
if (passphrase === void 0)
|
|
14426
|
+
throw new Refusal(`${file} holds a vault: set WAYFARER_VAULT_PASSPHRASE to its passphrase.`);
|
|
13172
14427
|
const dek = await unwrapDek(record, passphrase);
|
|
13173
14428
|
if (!dek) throw new Refusal(`WAYFARER_VAULT_PASSPHRASE is not the passphrase of the vault in ${file}.`);
|
|
13174
14429
|
return { dek, secrets: new Map(secrets.map((secret) => [secret.id, secret.envelope])) };
|
|
13175
14430
|
}
|
|
13176
14431
|
async function readSource(file, collection) {
|
|
13177
|
-
const text3 = await
|
|
14432
|
+
const text3 = await readText2(file, "file");
|
|
13178
14433
|
const parsed2 = parseJson(text3);
|
|
13179
14434
|
const id = parsed2.ok && typeof parsed2.value === "object" && parsed2.value !== null && "$id" in parsed2.value ? String(parsed2.value.$id) : "";
|
|
13180
14435
|
if (id !== "wayfarer/workspace" && !id.startsWith("wayfarer/workspace/")) {
|
|
13181
14436
|
try {
|
|
13182
14437
|
const { collections } = importText(text3);
|
|
13183
|
-
return {
|
|
14438
|
+
return {
|
|
14439
|
+
source: pick(
|
|
14440
|
+
collections.map(({ payload }) => ({ name: payload.collection.name, payload })),
|
|
14441
|
+
collection,
|
|
14442
|
+
"collection",
|
|
14443
|
+
"collection",
|
|
14444
|
+
file
|
|
14445
|
+
).payload,
|
|
14446
|
+
globals: []
|
|
14447
|
+
};
|
|
13184
14448
|
} catch (error) {
|
|
13185
14449
|
if (error instanceof Error && error.name === "ImportError") throw new Refusal(`${file}: ${error.message}`);
|
|
13186
14450
|
throw error;
|
|
13187
14451
|
}
|
|
13188
14452
|
}
|
|
13189
14453
|
const errors = validateWorkspaceFile(parsed2.ok ? parsed2.value : void 0);
|
|
13190
|
-
if (errors.length)
|
|
14454
|
+
if (errors.length)
|
|
14455
|
+
throw new Refusal(`${file} is not a workspace backup this version reads:
|
|
13191
14456
|
${errors.join("\n ")}`);
|
|
13192
14457
|
const { stores } = parsed2.ok ? parsed2.value : {};
|
|
13193
|
-
const chosen = pick(stores.collections, collection, "collection", file);
|
|
14458
|
+
const chosen = pick(stores.collections, collection, "collection", "collection", file);
|
|
13194
14459
|
const source = {
|
|
13195
14460
|
$id: "wayfarer/collection",
|
|
13196
14461
|
meta: chosen.meta,
|
|
@@ -13207,23 +14472,28 @@ async function readSource(file, collection) {
|
|
|
13207
14472
|
...environment2 && { environment: { name: environment2.name, vars: environment2.vars } },
|
|
13208
14473
|
globals: meta(META_GLOBALS_KEY)?.["variables"] ?? [],
|
|
13209
14474
|
// A backup's secrets are useful only with the passphrase: without one, a request that needs a secret says so.
|
|
13210
|
-
...vault && process.env["WAYFARER_VAULT_PASSPHRASE"] !== void 0 && {
|
|
14475
|
+
...vault && process.env["WAYFARER_VAULT_PASSPHRASE"] !== void 0 && {
|
|
14476
|
+
vault: await openVault(vault, stores.secrets, file)
|
|
14477
|
+
}
|
|
13211
14478
|
};
|
|
13212
14479
|
}
|
|
13213
|
-
async function readEnvironment(file) {
|
|
14480
|
+
async function readEnvironment(file, name) {
|
|
13214
14481
|
try {
|
|
13215
|
-
const { environments } = importText(await
|
|
13216
|
-
const
|
|
13217
|
-
return { name, vars };
|
|
14482
|
+
const { environments } = importText(await readText2(file, "environment"));
|
|
14483
|
+
const chosen = pick(environments, name, "environment", "environment-name", file);
|
|
14484
|
+
return { name: chosen.name, vars: chosen.vars };
|
|
13218
14485
|
} catch (error) {
|
|
13219
14486
|
if (error instanceof Error && error.name === "ImportError") throw new Refusal(`${file}: ${error.message}`);
|
|
13220
14487
|
throw error;
|
|
13221
14488
|
}
|
|
13222
14489
|
}
|
|
13223
14490
|
async function readVaultFile(file) {
|
|
13224
|
-
const parsed2 = parseJson(await
|
|
14491
|
+
const parsed2 = parseJson(await readText2(file, "vault file"));
|
|
13225
14492
|
const issues = parsed2.ok ? validateVaultFile(parsed2.value) : [{ path: "", message: "not JSON" }];
|
|
13226
|
-
if (issues.length)
|
|
14493
|
+
if (issues.length)
|
|
14494
|
+
throw new Refusal(
|
|
14495
|
+
`${file} is not a vault file ("$id": "${VAULT_FILE_FORMAT}"): ${issues.map((issue) => `${issue.path} ${issue.message}`.trim()).join("; ")}`
|
|
14496
|
+
);
|
|
13227
14497
|
const { vault, secrets } = parsed2.ok ? parsed2.value : {};
|
|
13228
14498
|
return openVault(vault, secrets, file);
|
|
13229
14499
|
}
|
|
@@ -13234,21 +14504,22 @@ function scriptHolders(source) {
|
|
|
13234
14504
|
...source.requests.filter((request) => scriptsOf(request).length).map((request) => `the request "${request.name}"`)
|
|
13235
14505
|
];
|
|
13236
14506
|
}
|
|
13237
|
-
function line(result, iterations) {
|
|
14507
|
+
function line(result, iterations, said2) {
|
|
13238
14508
|
const mark = runFailed(result) ? "\u2717" : result.skipped ? "-" : "\u2713";
|
|
13239
14509
|
const where = iterations > 1 ? ` (iteration ${result.iteration + 1})` : "";
|
|
13240
14510
|
const status = result.skipped ? "skipped" : result.status ? `${result.status} ${result.statusText ?? ""}`.trim() : "no response";
|
|
13241
14511
|
const tests2 = result.tests.map((test) => `
|
|
13242
14512
|
${test.passed ? "\u2713" : "\u2717"} ${test.name}${test.error ? `: ${test.error}` : ""}`).join("");
|
|
13243
|
-
const
|
|
13244
|
-
|
|
14513
|
+
const repeated = result.error !== void 0 && said2.has(result.error);
|
|
14514
|
+
if (result.error !== void 0) said2.add(result.error);
|
|
14515
|
+
const error = result.error === void 0 ? "" : `
|
|
14516
|
+
${repeated ? "Same error as before." : result.error}`;
|
|
13245
14517
|
return `${mark} ${result.name}${where}: ${status}${result.durationMs !== void 0 ? `, ${result.durationMs} ms` : ""}${tests2}${error}`;
|
|
13246
14518
|
}
|
|
13247
14519
|
function summary(report) {
|
|
13248
|
-
const
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
return `${report.results.length} results in ${report.iterations} iteration${report.iterations === 1 ? "" : "s"}; ${failed} failed; ${tests2.filter((test) => test.passed).length} of ${tests2.length} tests passed.${ended}`;
|
|
14520
|
+
const shared = sharedErrors(report.results).map(({ error, count }) => `
|
|
14521
|
+
${sharedErrorHeading(count)} ${error}`);
|
|
14522
|
+
return `${runEnding(report)} ${runCounts(report)}${shared.join("")}`;
|
|
13252
14523
|
}
|
|
13253
14524
|
async function main(argv) {
|
|
13254
14525
|
const { values, positionals } = parseArgs({
|
|
@@ -13256,6 +14527,7 @@ async function main(argv) {
|
|
|
13256
14527
|
allowPositionals: true,
|
|
13257
14528
|
options: {
|
|
13258
14529
|
environment: { type: "string", short: "e" },
|
|
14530
|
+
"environment-name": { type: "string" },
|
|
13259
14531
|
data: { type: "string", short: "d" },
|
|
13260
14532
|
iterations: { type: "string", short: "n" },
|
|
13261
14533
|
collection: { type: "string" },
|
|
@@ -13271,7 +14543,7 @@ async function main(argv) {
|
|
|
13271
14543
|
}
|
|
13272
14544
|
});
|
|
13273
14545
|
if (values.version) {
|
|
13274
|
-
console.log("2.0
|
|
14546
|
+
console.log("2.2.0");
|
|
13275
14547
|
return 0;
|
|
13276
14548
|
}
|
|
13277
14549
|
const [command, file, ...rest] = positionals;
|
|
@@ -13279,11 +14551,14 @@ async function main(argv) {
|
|
|
13279
14551
|
console.log(USAGE);
|
|
13280
14552
|
return values.help ? 0 : 2;
|
|
13281
14553
|
}
|
|
13282
|
-
if (command !== "run" || file === void 0 || rest.length)
|
|
14554
|
+
if (command !== "run" || file === void 0 || rest.length)
|
|
14555
|
+
throw new Refusal(`Unknown command or arguments: ${positionals.join(" ")}
|
|
13283
14556
|
|
|
13284
14557
|
${USAGE}`);
|
|
13285
14558
|
const reporters = new Set(values.reporter.split(",").map((name) => name.trim()));
|
|
13286
|
-
for (const name of reporters)
|
|
14559
|
+
for (const name of reporters)
|
|
14560
|
+
if (!["cli", "json", "junit", "html"].includes(name))
|
|
14561
|
+
throw new Refusal(`Unknown reporter "${name}": choose from cli, json, junit and html.`);
|
|
13287
14562
|
const number = (text3, flag2) => {
|
|
13288
14563
|
if (text3 === void 0) return void 0;
|
|
13289
14564
|
if (!/^\d+$/.test(text3)) throw new Refusal(`--${flag2} takes a whole number, not "${text3}".`);
|
|
@@ -13292,47 +14567,73 @@ ${USAGE}`);
|
|
|
13292
14567
|
const iterations = number(values.iterations, "iterations");
|
|
13293
14568
|
if (iterations === 0) throw new Refusal("--iterations takes 1 or more.");
|
|
13294
14569
|
const timeoutMs = number(values.timeout, "timeout") ?? 0;
|
|
14570
|
+
if (timeoutMs > NODE_FETCH_TIMEOUT_MS)
|
|
14571
|
+
throw new Refusal(`--timeout takes at most ${NODE_FETCH_TIMEOUT_MS}: Node's fetch waits no longer for a response.`);
|
|
13295
14572
|
const read = await readSource(file, values.collection);
|
|
13296
14573
|
const holders = scriptHolders(read.source);
|
|
13297
14574
|
if (holders.length && !values["allow-scripts"]) {
|
|
13298
|
-
throw new Refusal(
|
|
14575
|
+
throw new Refusal(
|
|
14576
|
+
`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.`
|
|
14577
|
+
);
|
|
13299
14578
|
}
|
|
13300
|
-
|
|
14579
|
+
if (values["environment-name"] !== void 0 && values.environment === void 0)
|
|
14580
|
+
throw new Refusal("--environment-name names an environment of the file given with --environment.");
|
|
14581
|
+
const environment2 = values.environment === void 0 ? read.environment : await readEnvironment(values.environment, values["environment-name"]);
|
|
13301
14582
|
const vault = values["vault-file"] === void 0 ? read.vault : await readVaultFile(values["vault-file"]);
|
|
13302
14583
|
let data;
|
|
13303
14584
|
if (values.data !== void 0) {
|
|
13304
14585
|
try {
|
|
13305
|
-
data = parseDataFile(await
|
|
14586
|
+
data = parseDataFile(await readText2(values.data, "data file"), basename(values.data));
|
|
13306
14587
|
} catch (error) {
|
|
13307
|
-
if (error instanceof Error && error.name === "DataFileError")
|
|
14588
|
+
if (error instanceof Error && error.name === "DataFileError")
|
|
14589
|
+
throw new Refusal(`${values.data}: ${error.message}`);
|
|
13308
14590
|
throw error;
|
|
13309
14591
|
}
|
|
13310
14592
|
}
|
|
13311
14593
|
if (values.insecure) process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";
|
|
13312
14594
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13313
|
-
if (reporters.has("json") || reporters.has("junit")) {
|
|
14595
|
+
if (reporters.has("json") || reporters.has("junit") || reporters.has("html")) {
|
|
13314
14596
|
await mkdir(values.out, { recursive: true }).catch((error) => {
|
|
13315
14597
|
throw new Refusal(`--out ${values.out}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13316
14598
|
});
|
|
13317
14599
|
}
|
|
14600
|
+
const said2 = /* @__PURE__ */ new Set();
|
|
13318
14601
|
const report = await runCollection(
|
|
13319
|
-
{
|
|
14602
|
+
{
|
|
14603
|
+
source: read.source,
|
|
14604
|
+
...environment2 && { environment: environment2 },
|
|
14605
|
+
globals: read.globals,
|
|
14606
|
+
...vault && { vault },
|
|
14607
|
+
allowScripts: values["allow-scripts"],
|
|
14608
|
+
timeoutMs
|
|
14609
|
+
},
|
|
13320
14610
|
{
|
|
13321
14611
|
...iterations !== void 0 && { iterations },
|
|
13322
14612
|
...data && { data },
|
|
13323
14613
|
stopOnFailure: values.bail,
|
|
13324
14614
|
onResult: (result) => {
|
|
13325
|
-
if (reporters.has("cli")) console.log(line(result, iterations ?? data?.length ?? 1));
|
|
14615
|
+
if (reporters.has("cli")) console.log(line(result, iterations ?? data?.length ?? 1, said2));
|
|
13326
14616
|
}
|
|
13327
14617
|
}
|
|
13328
14618
|
);
|
|
13329
14619
|
const info = { name: read.source.collection.name, startedAt };
|
|
13330
14620
|
if (reporters.has("cli")) console.log(`
|
|
13331
14621
|
${summary(report)}`);
|
|
13332
|
-
|
|
13333
|
-
|
|
13334
|
-
|
|
13335
|
-
|
|
14622
|
+
const code = report.results.some(runFailed) ? 1 : 0;
|
|
14623
|
+
try {
|
|
14624
|
+
if (reporters.has("json"))
|
|
14625
|
+
await writeFile(
|
|
14626
|
+
join(values.out, "wayfarer-run.json"),
|
|
14627
|
+
`${JSON.stringify(runReportJson(report, info), null, 2)}
|
|
14628
|
+
`
|
|
14629
|
+
);
|
|
14630
|
+
if (reporters.has("junit")) await writeFile(join(values.out, "wayfarer-run.junit.xml"), junitXml(report, info));
|
|
14631
|
+
if (reporters.has("html")) await writeFile(join(values.out, "wayfarer-run.html"), runReportHtml(report, info));
|
|
14632
|
+
} catch (error) {
|
|
14633
|
+
console.error(`--out ${values.out}: ${error instanceof Error ? error.message : String(error)}`);
|
|
14634
|
+
return 1;
|
|
14635
|
+
}
|
|
14636
|
+
return code;
|
|
13336
14637
|
}
|
|
13337
14638
|
main(process.argv.slice(2)).then(
|
|
13338
14639
|
(code) => process.exitCode = code,
|