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.
@@ -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 = "The file is larger than 10 MB.";
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 decodeResponseBody(bytes, contentType2) {
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 decodeEnvelope(envelope) {
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 decodeResponseBody(envelope.body, contentType2);
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(value, path.replace(/\[\*\]/g, `.${EVERY}`).replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean));
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
- const bytes = new TextEncoder().encode(`${auth3.username}:${auth3.password}`);
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 { ...auth3, accessKey: resolve(auth3.accessKey), secretKey: resolve(auth3.secretKey), sessionToken: resolve(auth3.sessionToken), region: resolve(auth3.region), service: resolve(auth3.service) };
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/auth/sigv4.ts
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
- var sha256 = async (data) => new Uint8Array(await crypto.subtle.digest("SHA-256", data));
7658
- var hmac = async (key, data) => new Uint8Array(await crypto.subtle.sign("HMAC", await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]), utf8(data)));
7659
- var hex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
7660
- var encode = (text3) => encodeURIComponent(text3).replace(/[!'()*]/g, (mark) => `%${mark.charCodeAt(0).toString(16).toUpperCase()}`);
7661
- var decode = (text3) => {
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 decodeURIComponent(text3);
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 (!(error instanceof URIError)) throw error;
7666
- return text3;
7704
+ if (error instanceof Error && error.name === "OperationError") return null;
7705
+ throw error;
7667
7706
  }
7668
- };
7669
- async function signV4(request, credentials, at) {
7670
- const url = new URL(request.url);
7671
- const s3 = credentials.service === "s3";
7672
- const segments = url.pathname.split("/");
7673
- const kept = segments.filter(Boolean);
7674
- const path = s3 ? segments.map((segment) => encode(decode(segment))).join("/") : `/${kept.map(encode).join("/")}${kept.length && url.pathname.endsWith("/") ? "/" : ""}`;
7675
- const query = url.search.slice(1).split("&").filter(Boolean).map((pair) => {
7676
- const cut = pair.indexOf("=");
7677
- return [encode(decode(cut === -1 ? pair : pair.slice(0, cut))), encode(decode(cut === -1 ? "" : pair.slice(cut + 1)))];
7678
- }).sort(([keyA, valueA], [keyB, valueB]) => keyA === keyB ? valueA < valueB ? -1 : valueA > valueB ? 1 : 0 : keyA < keyB ? -1 : 1).map(([key2, value]) => `${key2}=${value}`).join("&");
7679
- const date = at.toISOString().replace(/[-:]|\.\d{3}/g, "");
7680
- const day = date.slice(0, 8);
7681
- const body3 = typeof request.body === "string" ? utf8(request.body) : new Uint8Array(request.body ?? []);
7682
- const bodyHash = hex(await sha256(body3));
7683
- const added2 = [["X-Amz-Date", date], ...credentials.sessionToken ? [["X-Amz-Security-Token", credentials.sessionToken]] : [], ...s3 ? [["X-Amz-Content-Sha256", bodyHash]] : []];
7684
- const byName = /* @__PURE__ */ new Map();
7685
- const own3 = request.headers.filter(([name]) => !added2.some(([written]) => written.toLowerCase() === name.toLowerCase()) && name.toLowerCase() !== "host");
7686
- for (const [name, value] of [["Host", url.host], ...own3, ...added2]) {
7687
- const key2 = name.toLowerCase();
7688
- byName.set(key2, [...byName.get(key2) ?? [], value.trim().replace(/\s+/g, " ")]);
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
- const names = [...byName.keys()].sort();
7691
- const signedHeaders = names.join(";");
7692
- const canonicalRequest = [request.method, path, query, ...names.map((name) => `${name}:${(byName.get(name) ?? []).join(",")}`), "", signedHeaders, bodyHash].join("\n");
7693
- const scope = `${day}/${credentials.region}/${credentials.service}/aws4_request`;
7694
- const stringToSign = ["AWS4-HMAC-SHA256", date, scope, hex(await sha256(utf8(canonicalRequest)))].join("\n");
7695
- let key = utf8(`AWS4${credentials.secretKey}`);
7696
- for (const part of [day, credentials.region, credentials.service, "aws4_request"]) key = await hmac(key, part);
7697
- const signature = hex(await hmac(key, stringToSign));
7698
- return {
7699
- headers: [...added2, ["Authorization", `AWS4-HMAC-SHA256 Credential=${credentials.accessKey}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`]],
7700
- canonicalRequest,
7701
- stringToSign,
7702
- signature
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([auth3.grant, auth3.tokenUrl, auth3.clientId, auth3.scope, auth3.grant === "password" ? auth3.username : ""]);
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, parent) {
7729
- const entries = [
7730
- ...tree.folders.filter((folder) => folder.parentFolderId === parent).map((folder) => ({ order: folder.order, kind: 0, id: folder.id, child: { folder } })),
7731
- ...tree.requests.filter((request) => request.folderId === parent).map((request) => ({ order: request.order, kind: 1, id: request.id, child: { request } }))
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 }) => ({ name: `${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 = ["inherit", "none", "bearer", "basic", "apikey", "awsv4", "oauth2"];
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 field(object, key) {
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 = field(object, key);
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 = field(object, key);
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 field(row, "enabled") !== "boolean") {
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") && (field(row, "json") !== true || !isJsonText(field(row, "value")))) this.fail(`${rowPath}.json`, "Value must be true, on a value that is 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 = field(auth3, "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"]) this.string(auth3, key, path);
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 field(auth3, "retryOn401") !== "boolean") this.fail(`${path}.retryOn401`, "Value must be true or false.");
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(field(node, "auth"), `${path}.auth`, kind === "folder");
7879
- check.scripts(field(node, "scripts"), `${path}.scripts`);
7880
- if (kind === "folder") check.rows(field(node, "variables"), `${path}.variables`);
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 = field(request, "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 field(request, "url") !== "string" || !field(request, "url").trim()) {
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(field(request, "params"), `${path}.params`);
7896
- check.rows(field(request, "headers"), `${path}.headers`);
7897
- const body3 = check.object(field(request, "body"), `${path}.body`);
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 = field(body3, "mode");
8024
+ const mode = field2(body3, "mode");
7900
8025
  check.oneOf(body3, "mode", `${path}.body`, BODY_MODES);
7901
- const part = (name) => field(body3, name) !== void 0 || mode === name ? name : null;
8026
+ const part = (name) => field2(body3, name) !== void 0 || mode === name ? name : null;
7902
8027
  if (part("raw")) {
7903
- const raw = check.object(field(body3, "raw"), `${path}.body.raw`);
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(field(body3, "urlencoded"), `${path}.body.urlencoded`);
8035
+ check.rows(field2(body3, "urlencoded"), `${path}.body.urlencoded`);
7911
8036
  }
7912
8037
  if (part("multipart")) {
7913
- check.list(field(body3, "multipart"), `${path}.body.multipart`, (item, itemPath) => {
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 (field(item, "kind") === "text") check.string(item, "value", itemPath);
7918
- if (field(item, "kind") === "file") check.fileRef(item, itemPath);
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(field(body3, "graphql"), `${path}.body.graphql`);
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 (field(body3, "binary") !== void 0) {
7929
- const binary = check.object(field(body3, "binary"), `${path}.body.binary`);
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(field(request, "auth"), `${path}.auth`, true);
7937
- check.scripts(field(request, "scripts"), `${path}.scripts`);
7938
- check.list(field(request, "tests"), `${path}.tests`, (test, testPath) => {
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(field(request, "settings"), `${path}.settings`);
8070
+ const settings = check.object(field2(request, "settings"), `${path}.settings`);
7946
8071
  if (settings) {
7947
- const timeout = field(settings, "timeoutMs");
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 = field(settings, "followRedirects");
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 = field(settings, "tlsInsecure");
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.id ?? payload.collection.meta.id,
8171
+ id: payload.collection.meta.id,
8051
8172
  action
8052
8173
  }
8053
8174
  ];
8054
- for (const folder of payload.folders) {
8055
- entries.push({
8056
- type: "folder",
8057
- name: folder.name,
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
- if (!doc.id) {
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)) return { error: "The GraphQL variables must be a JSON object." };
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]) => [this.text(name), !options.credentials && isCredentialHeader(name) ? MASK : this.text(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) => ({ ...row, value: isCredentialHeader(row.key) ? this.credential(row.value, options) : this.text(row.value) }));
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 { ...auth3, secretKey: this.credential(auth3.secretKey, options), sessionToken: this.credential(auth3.sessionToken, options) };
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 { ...auth3, clientSecret: this.credential(auth3.clientSecret, options), password: this.credential(auth3.password, options) };
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 && { graphql: { query: this.text(body3.graphql.query), variables: this.text(body3.graphql.variables) } },
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 && { multipart: body3.multipart.map((part) => part.kind === "text" ? { ...part, value: this.text(part.value) } : part) }
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))) continue;
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) if (row.enabled && row.key) byName.set(row.key, row.json ? [row.key, row.value, true] : [row.key, row.value]);
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
- return [...this.tokensOf(payload.url ?? "", "url", "endpoint"), ...header, ...body3];
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
- timedOut = true;
8713
- controller.abort();
8714
- }, options.timeoutMs) : void 0;
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) throw new TransportError("timeout", `Timed out after ${options.timeoutMs} ms`);
8720
- if (options.signal.aborted) throw new TransportError("aborted", "The request was cancelled.");
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 readBody(response, cap = DISPLAY_CAP_BYTES) {
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 stopped = false;
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 }))) onEvent({ ...event, timeMs: Date.now() - started });
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
- stopped = true;
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
- return withDeadline(options, async (signal, arrived) => {
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 readBody(response, this.displayCap);
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 hmac2 = /* @__PURE__ */ (() => {
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 sha2562 = /* @__PURE__ */ createHasher(
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: sha2562 };
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")) throw new TypeError("Expected a WordArray.");
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(hmac2(HASHES[name], bytesOf(keyFormat, key), bytesOf(format, payload)));
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") return toWords(Uint8Array.from({ length: text3.length >>> 1 }, (_, i) => parseInt(text3.slice(i * 2, i * 2 + 2), 16) || 0));
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 { initial: ENGINE_INITIAL_PAGES, maximum: Math.max(ENGINE_INITIAL_PAGES, Math.floor(limits.memoryBytes / WASM_PAGE)) };
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: { local: scope(), data: scope(context.data), environment: scope(context.environment), folder: scope(context.folder), collection: scope(context.collection), global: scope(context.globals) },
10058
- json: { local: marked(), data: marked(context.data), environment: marked(context.environment), folder: marked(context.folder), collection: marked(context.collection), global: marked(context.globals) },
10059
- changes: { local: /* @__PURE__ */ new Map(), data: /* @__PURE__ */ new Map(), environment: /* @__PURE__ */ new Map(), folder: /* @__PURE__ */ new Map(), collection: /* @__PURE__ */ new Map(), global: /* @__PURE__ */ new Map() },
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]) => ({ key, value, ...value !== null && state.json[name].has(key) && { json: true } }));
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") return result(void 0, "stack");
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)) return { message: "", limit: "memory" };
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) throw new RangeError("Script wrote too many test results and variables.");
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(([key, value]) => `[${JSON.stringify(key)},${state.json[name].has(key) ? value : JSON.stringify(value)}]`);
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)) throw new Error(`require('${id}'): write the module's name out in the script, as require('${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("hmac", (name, keyFormat, key, format, payload) => vm.newString(vmHmac(hash(name), text3(keyFormat), text3(key), text3(format), text3(payload))));
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) => ({ name: error instanceof Error ? error.name : "Error", message: error instanceof Error ? error.message : String(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, { name: "Error", message: `pm.sendRequest: a script may make ${limits.maxSendRequests} requests, and this is one more.` });
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((pair) => Array.isArray(pair) && pair.length === 2 && typeof pair[0] === "string" && typeof pair[1] === "string");
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"])) throw new TypeError("The request's method is not an HTTP 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 { method: request["method"], url: request["url"], headers: request["headers"], ...body3 !== void 0 && { body: body3 } };
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 = { method: request["method"], url: request["url"], headers: request["headers"] };
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)) return { ...base, body: { mode: "urlencoded", urlencoded: body3.urlencoded } };
10395
- if (body3?.mode === "none" || body3?.mode === "multipart" || body3?.mode === "binary") return { ...base, body: { mode: body3.mode } };
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 inOrder = (parent) => childrenInOrder(tree, parent).flatMap((child) => {
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) => ({ iterations: count, results, ended, ...reason !== void 0 && { 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
- percentDecoded(request.url),
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]) => percentDecoded(decodeBasicCredentials(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 percentDecoded(text3) {
10497
- try {
10498
- return decodeURIComponent(text3);
10499
- } catch (error) {
10500
- if (!(error instanceof URIError)) throw error;
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, percentDecoded(body3.replaceAll("+", " "))];
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 NETWORK_ERROR_TEXT = "Network error \u2014 no response was received. The host may not resolve (DNS), may have refused the connection, or may not allow cross-origin requests from this site (CORS). The browser does not reveal which. Check the URL and your connection; for CORS, the Local Bridge can relay the request.";
10686
- var networkErrorWithoutPage = (reason) => `Network error \u2014 no response was received: ${reason}. Check the URL, that the host resolves, and that it accepts connections.`;
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(([name, value]) => typeof value === "string" ? `${name}=${value}` : `${name}=@${value.name} ${file(value)}`).join("\n");
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((last, run) => run.result.nextRequest !== void 0 ? run.result.nextRequest : last, void 0);
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) => ({ ...row, label: redactor.text(row.label), ...row.error !== void 0 && { error: redactor.text(row.error) } }));
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 = { environment: ports.environment()?.vars ?? [], global: ports.globals(), collection: spec.collection?.variables() ?? [] };
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(ports, spec, { environment: was(before.environment, touched.environment), global: was(before.global, touched.global), collection: was(before.collection, touched.collection) }, []);
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(text3, environmentSnapshot(ports), void 0, scriptExtras(ports, spec, "prerequest", scriptRequestOf(template)));
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(preRuns.flatMap((run) => scriptRows(run.result, run.name, nothing)), [...preRuns.flatMap((run) => run.result.logs), ...notes], timings, name);
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(preRuns.flatMap((run) => scriptRows(run.result, run.name, nothing)), [...preRuns.flatMap((run) => run.result.logs), ...notes], lastNext(preRuns));
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((cookie) => !containsSecretPlaceholder({ url: "", headers: [["Cookie", `${cookie.name}=${cookie.value}`]] }));
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 = { route: spec.route ?? ports.transport.route(), ...spec.askRoute && { ask: spec.askRoute } };
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, request.url, ports.page) : shapeResponse(outcome, request.url, ports.page);
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
- { statusCode: shaped.response.statusCode ?? 0, statusText: shaped.response.statusText ?? "", body: shaped.body, headers: shaped.headers, durationMs, sizeBytes: outcome instanceof TransportError ? void 0 : outcome.sizes.decoded }
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 shapeResponse(envelope, url, page) {
10898
- const body3 = decodeEnvelope(envelope);
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 failureText = `Http failure response for ${url}: ${envelope.status} ${envelope.statusText}`;
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 : void 0,
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
- headersLimited: page !== void 0 && envelope.route === "direct" && URL.parse(envelope.finalUrl, page)?.origin !== URL.parse(page)?.origin,
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, url, page) {
11323
+ function shapeFailure(failure, page, online) {
10934
11324
  const network = failure.kind === "network";
10935
- const statusCode = network ? 0 : failure.kind === "bridge" ? failure.status ?? 0 : void 0;
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: network ? void 0 : failure.message,
11328
+ body: said2 ? void 0 : failure.message,
10938
11329
  headers: {},
10939
11330
  headerList: [],
10940
- historyError: network ? `Http failure response for ${url}: 0 Unknown Error` : failure.message,
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: network ? "Unknown Error" : "",
11334
+ statusCode: void 0,
11335
+ statusText: "",
10945
11336
  bodyIsJson: false,
10946
11337
  dataText: "",
10947
- errorText: !network ? failure.message : page === void 0 ? networkErrorWithoutPage(failure.message) : NETWORK_ERROR_TEXT,
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(text3, environmentSnapshot(ports), response, scriptExtras(ports, spec, "test", sent));
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 { scripts: scripts2, assertions: spec.tests.length ? runAssertions(spec.tests, { statusCode, body: body3, headers: headers2, durationMs }) : [] };
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: { eventName, requestName: spec.info?.requestName ?? "", requestId: spec.info?.requestId ?? "", ...spec.iteration && { iteration: spec.iteration.index, iterationCount: spec.iteration.count } },
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(new Error("pm.sendRequest with a vault secret is not supported \u2014 see docs/postman-compatibility.md#pm-sendrequest"), { name: "WayfarerUnsupportedError" });
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 envelope = await ports.transport.send(request, { signal: signal ?? new AbortController().signal, timeoutMs: ports.timeoutMs() }, { route });
10985
- const shaped = shapeResponse(envelope, request.url, ports.page);
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 }) => ({ key, value: value === null || !secrets ? value : secrets.text(value), keepSecret: true, ...json && { 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 notes.push("[warn] pm.collectionVariables: this request is in no collection, so what the script set was not kept.");
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)) refused("the secret this variable refers to is not in the vault");
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(`${names.map((name) => `{{${name}}}`).join(", ")} ${names.length === 1 ? "has" : "have"} no value. The request was not sent.`);
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
- if (!probe.lockedSecrets.size) return checked(new VariableResolver(scopes()));
11149
- const withSecrets = () => context.readSecrets([...probe.lockedSecrets]).then((secrets) => checked(new VariableResolver(scopes(), (id) => secrets.get(id))));
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) throw new SendDeclinedError("The request was not sent, and what the pre-request script set was undone.");
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(assemble(content, body3, resolver, { ancestors: context.ancestors, token: token2, lastEventId: context.lastEventId }), auth3);
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) return sent instanceof Promise ? sent.then((body3) => finish(body3)) : finish(sent);
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(`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.`);
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("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.");
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) throw new SendBlockedError("AWS Signature needs the browser's cryptography, which a page has only over https or from localhost. Nothing was sent.");
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({ method: built2.method, url: built2.url, headers: own3, body: body3 }, auth3, /* @__PURE__ */ new Date());
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: [...built2.credentials, ...signature.filter(([name]) => isCredentialHeader(name)).map(([, value]) => value)]
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: [...credentialsOf(auth3), ...headers2.filter(([name]) => isCredentialHeader(name)).map(([, value]) => value)].filter((value) => value)
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((body3.multipart ?? []).filter((item) => item.enabled), resolve, context);
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) return { outcome: { skipped: true, tests: tests(error.testResults), ...error.nextRequest !== void 0 && { nextRequest: error.nextRequest } } };
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) return { outcome: { error: error.message, tests: tests(error.testResults) } };
11290
- if (error instanceof SendBlockedError || error instanceof VariableNestingError) return { outcome: { error: error.message, tests: [] } };
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] = ["collections", "folders", "requests", "environments", "secrets", "meta"].map(list4);
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(...(result.errors ?? []).map((issue) => `collection "${String(collection["name"])}": ${issue.path}: ${issue.message}`));
11331
- if (!isObject2(collection["scriptTrust"])) errors.push(`collection "${String(collection["name"])}": scriptTrust is missing.`);
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 [["folders", folders], ["requests", requests]]) {
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"])) errors.push(`stores.${kind}[${index}]: belongs to a collection that is not in the file.`);
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") errors.push("stores.meta: the active environment must be an id or null.");
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(...validateRows(record["variables"], "stores.meta.globals.variables").map((issue) => `${issue.path}: ${issue.message}`));
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"]) errors.push(`stores.secrets[${index}]: meta.id must be the secret's 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") errors.push(`${path}.route: Value must be direct or 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(...validateRequestContent(entry["template"], `${path}.template`).map((issue) => `${issue.path}: ${issue.message}`));
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)) throw new DataFileError("A JSON data file is a list of objects, one for each iteration.");
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(([key, value]) => typeof value === "string" ? { key, value, enabled: true } : { key, value: stringifyJson(value) ?? "null", enabled: true, json: true })
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)) throw new DataFileError("A CSV data file has the names of its values in its first line.");
11424
- return records.filter((record) => record.length > 1 || record[0] !== "").map((record) => names.flatMap((key, index) => key ? [{ key, value: record[index] ?? "", enabled: true }] : []));
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 = { n: "\n", t: " ", r: "\r", a: "\x07", b: "\b", e: "\x1B", f: "\f", v: "\v", "\\": "\\", "'": "'", '"': '"', "?": "?" };
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(["-s", "--silent", "-S", "--show-error", "-v", "--verbose", "-i", "--include", "-L", "--location", "-f", "--fail", "-#", "--progress-bar", "-g", "--globoff", "-N", "--no-buffer", "--http1.1", "--http2", "-4", "-6", "-q", "-n", "-j"]);
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(`The form part ${name} takes its text from the file ${content.slice(1).split(";")[0]}, which was not read: the part is empty.`);
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
- if (kind === "data") binaryOnly = false;
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)) warnings.push(`${word} ${valueOf(word)} was left out: the app has nothing it applies to.`);
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) warnings.push(`The command names ${urls.length} addresses. The first is used; the others were left out: ${urls.slice(1).join(", ")}.`);
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) warnings.push("The command has both a form (-F) and data (-d). curl refuses that; the form is kept.");
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
- body3 = !type || type.startsWith("application/x-www-form-urlencoded") ? { mode: "urlencoded", fields: [...new URLSearchParams(wire)] } : { mode: "raw", text: wire };
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)) throw new ImportError(`The cURL command's method, ${method}, is not an HTTP method: one word of at most 32 characters.`);
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") warnings.push("-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.");
11783
- if (kind === "compressed") warnings.push("--compressed was left out: the browser asks for a compressed answer by itself.");
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(([name, value]) => !(name.toLowerCase() === "content-type" && implied !== null && value.toLowerCase().split(";")[0].trim() === implied));
11803
- const content = { ...emptyRequest(), method: request.method, url: request.url, headers: headers2.map(([name, value]) => row(name, value)) };
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((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 })
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) if ("fileName" in part) warnings.push(`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.`);
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 = { mode: "binary", ...type && { binary: { fileId: "", fileName: body3.fileName, contentType: type } } };
11817
- warnings.push(`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.`);
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("Its multipart body is in the file as text only, with a boundary of that one send. It is kept as text.");
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((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")) })
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 { mode: "urlencoded", fields: params.length ? params.map((param) => [text(own2(param, "name")), text(own2(param, "value"))]) : [...new URLSearchParams(text(own2(postData, "text")))] };
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) throw new ImportError(`The HAR's entry ${index + 1} has no request with a method and a 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 [{ name: `${method} ${path}`, request: { method, url, headers: headers2, body: bodyOf(own2(request, "postData"), warnings) }, warnings }];
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 && { auth: { user: back(user.split(":")[0]), ...user.includes(":") && { password: back(user.slice(user.indexOf(":") + 1)) } } },
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) => ({ key: text2(field3(entry, "key") ?? field3(entry, "id")), value: text2(field3(entry, "value")), enabled: field3(entry, "disabled") !== true }));
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 { type, accessKey: read("accessKey"), secretKey: read("secretKey"), sessionToken: read("sessionToken"), region: read("region"), service: read("service") };
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({ item, 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.` });
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")) warnings.push({ item, message: "The OAuth 2.0 token in the file is not kept: get one in the Auth tab." });
11967
- if (read("addTokenTo") === "queryParams") warnings.push({ item, message: "Its OAuth 2.0 token was added to the address: the app sends it in the Authorization header." });
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({ item, 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.` });
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) warnings.push({ item, message: `A script for "${others[0]}" is left out: the app runs a script before a request and after its response.` });
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((piece, index) => index % 2 ? piece : piece.replace(marks, (mark) => `%${mark.charCodeAt(0).toString(16).toUpperCase()}`)).join("");
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 { key: enabled ? kept(text2(field3(entry, "key")), /[#&=]/g) : text2(field3(entry, "key")), value: enabled ? kept(text2(field3(entry, "value")), /[#&]/g) : text2(field3(entry, "value")), enabled };
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) return { url: text2(field3(value, "raw")), params: params.length ? params : inText(text2(field3(value, "raw"))) };
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((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)}`);
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) warnings.push({ item, 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.` });
12036
- if (named2.length) warnings.push({ item, 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"}.` });
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")) && { binary: { fileId: "", fileName: file(field3(field3(value, "file"), "src")) } },
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")) && { graphql: { query: text2(field3(field3(value, "graphql"), "query")), variables: text2(field3(field3(value, "graphql"), "variables")) } }
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) warnings.push({ item, message: "A file of its body is named, not included: choose it again in the Body tab." });
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({ item, message: `Its body is of a kind the app does not have (${text2(field3(value, "mode"))}) and is left out.` });
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({ item: item2, 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.` });
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) warnings.push({ item, message: `${examples} saved ${examples === 1 ? "response is" : "responses are"} left out: the app keeps responses in its history, not in a collection.` });
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) => ({ key: text2(field3(entry, "key")), value: text2(field3(entry, "value")), enabled: field3(entry, "enabled") !== false && field3(entry, "disabled") !== true }))
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 ? [{ item: `environment "${name}"`, 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." }] : []
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(VARIABLE, (_match, path) => `{{${path.replace(/\[(?:'([^'\]]*)'|"([^"\]]*)")\]/g, (_part, single, double) => `.${single ?? double ?? ""}`).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) warnings.push({ item, 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.` });
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) => ({ key: said2(field3(entry, "name")), value: said2(field3(entry, "value")), ...field3(entry, "disabled") === true && { disabled: true } })).filter((entry) => entry.key !== "" || entry.value !== "");
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 { auth: { type: "apikey", apikey: { key: "Authorization", value: prefix === "NO_PREFIX" ? read("token") : `${prefix} ${read("token")}`, in: "header" } } };
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 { auth: { type, apikey: { key: read("key"), value: read("value"), in: place === "queryParams" ? "query" : "header" } } };
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 { auth: { type: "awsv4", awsv4: { accessKey: read("accessKeyId"), secretKey: read("secretAccessKey"), sessionToken: read("sessionToken"), region: read("region"), service: read("service") } } };
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") return { auth: { type, oauth2: { grant_type: grant || "none said" } } };
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)) return { body: { mode: "urlencoded", urlencoded: pairs(params, said2) } };
12295
- if (typeof field3(value, "fileName") === "string" && typeof field3(value, "text") !== "string") return { body: { mode: "file", file: { src: field3(value, "fileName") } } };
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) warnings.push({ item, 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.` });
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: { ...url, query: [...url.query ?? [], ...pairs(field3(source, "parameters"), said2)], variable: pairs(field3(source, "pathParameters"), said2) },
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) warnings.push({ item, message: `Its headers (${headers2.map((header) => header.key).join(", ").slice(0, 200)}) 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.` });
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) warnings.push({ item, 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.` });
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)) throw new ImportError(`This Insomnia file (${type.slice(0, 60)}) holds no requests and no environments, so there is nothing to import.`);
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 = { source, name: text2(field3(source, "name")).trim(), description: described(source), scripts: { pre: field3(own3, "preRequest"), post: field3(own3, "afterResponse") } };
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({ item: `request "${entry.name || "Request"}"`, message: "It is not an HTTP request (gRPC, WebSocket or Socket.IO) and is left out." });
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) => ({ name: text2(field3(sub, "name")).trim() || "Environment", vars: rowsOf(field3(sub, "data"), said2) }));
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) warnings.push({ item, 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.` });
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 = { collections: [{ info: { name, description: described(value) }, variable: base, item: level(tree, 1) }], environments: subs.map((sub) => environment(sub.name, sub.vars)), warnings };
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) => [...own3, ...base.filter((row) => !own3.some((other) => other.key === row.key))];
12384
- result = { collections: [], environments: [environment(name, base), ...subs.map((sub) => environment(sub.name, merged(sub.vars)))], warnings };
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(`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.`);
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
- children.set(parent, [...children.get(parent) ?? [], resource]);
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 = { source, name: text2(field3(source, "name")).trim(), description: field3(source, "description"), scripts: { pre: field3(source, "preRequestScript"), post: field3(source, "afterResponseScript") } };
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 = { info: { name, description: field3(workspace, "description") }, variable: under(id(workspace), "environment").flatMap((base) => rowsOf(field3(base, "data"), said2)), item: level(id(workspace), 1) };
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(resources.filter((resource) => kind(resource) === "workspace" || kind(resource) === "request_group").map(id));
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((resource) => (kind(resource) === "request" || kind(resource) === "request_group") && !placed.has(resource)).length;
12439
- if (unplaced) warnings.push({ item: "file", 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.` });
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")) warnings.push({ item: `request "${text2(field3(resource, "name")).trim() || "Request"}"`, message: "It is not an HTTP request (gRPC, WebSocket or Socket.IO) and is left out." });
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 = [...new Set(resources.filter((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())).map((resource) => kind(resource).slice(0, 30)))];
12444
- if (others.length) warnings.push({ item: "file", message: `What the file holds that is not a request, a folder or an environment is left out (${others.slice(0, 8).join(", ")}).` });
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(`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.`);
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)) warnings.push({ item: top, message: `The file is written for OpenAPI ${version}: it is read as OpenAPI 3.1, and what is newer is left out.` });
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"]) if (field3(schema, key) !== void 0) return field3(schema, key);
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) return parts.some(isObject4) ? Object.fromEntries(parts.filter(isObject4).flatMap((part) => Object.entries(part))) : parts[0];
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/") ? { mode: "formdata", formdata: fields.map(({ key, schema, value }) => isFile(schema) ? { key, type: "file", src: "" } : { key, type: "text", value: said(value) }) } : { mode: "urlencoded", urlencoded: fields.map(({ key, value }) => ({ key, value: said(value) })) };
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 { body: form(kind, Object.keys(isObject4(value) ? value : {}).map((key) => ({ key, schema: isObject4(properties) ? field3(properties, key) : void 0, value: value[key] }))), header: [] };
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)) return { body: { mode: "file", file: { src: "" } }, header: [] };
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) warnings.push({ item, message: `Its body (${type.slice(0, 80)}) has no example in the file that the app can write out: the body is empty.` });
12597
- return { body: { mode: "raw", raw, options: { raw: { language } } }, header: kind.includes("*") || RAW_CONTENT_TYPES[language] === kind ? [] : [{ key: "Content-Type", value: type }] };
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) warnings.push({ item, message: `It needs ${names.length} security schemes together (${named(names)}): only the first is set.` });
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") return { auth: { type: "basic", basic: { username: "{{authUsername}}", password: "{{authPassword}}" } } };
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") return { auth: { type: `an API key in a ${place.slice(0, 20) || "place the file does not say"}` } };
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)) return { auth: { type, oauth2: { grant_type: (swagger ? text2(field3(scheme, "flow")) : Object.keys(isObject4(flows) ? flows : {})[0]) || "none said" } } };
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(`${host && `${text2(list3(field3(root, "schemes"))[0] ?? "https")}://${host}`}${text2(field3(root, "basePath"))}`) : server(field3(root, "servers")) ?? "";
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({ item: top, 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.` });
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) warnings.push({ item: top, message: `The file names ${others + 1} servers: the collection's variable baseUrl holds the first (${base.slice(0, 80)}).` });
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)) folders.set(text2(field3(tag, "name")), { description: field3(tag, "description"), item: [] });
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"))]) parameters.set(`${text2(field3(parameter, "in"))}:${text2(field3(parameter, "name"))}`, parameter);
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)) header.push({ key, value: said(value), ...field3(parameter, "required") !== true && said(value) === "" && { disabled: true } });
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(consumes.some((type) => type.startsWith("multipart/")) || fields.some(({ schema }) => isFile(schema)) ? "multipart/form-data" : "application/x-www-form-urlencoded", fields);
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) warnings.push({ item, message: `Its body is described in ${types.length} media types (${named(types)}): the request has the ${type.slice(0, 80)} one.` });
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) warnings.push({ item, message: `It has ${tags.length} tags (${named(tags)}): it is in the folder of the first.` });
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: { host: [own3 ?? "{{baseUrl}}"], path: path.replace(/^\//, "").split("/").map((part) => part.replace(/\{([^{}]+)\}/g, "{{$1}}")), query },
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 = { info: { name, description: isObject4(info) ? field3(info, "description") : void 0 }, variable: [{ key: "baseUrl", value: base }], ...auth3(field3(root, "security")) };
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) throw new ImportError("This OpenAPI file describes no operation under `paths`, so there is no request to import.");
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) warnings.push({ item: where, 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.` });
12739
- if (missing.size) warnings.push({ item: where, 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.` });
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 = "The file is not JSON, and at more than 1 MB it is too large to be read as YAML: a YAML file takes far more memory to read than a JSON file of its size. If it is YAML, convert it to JSON and import that.";
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 (0, import_yaml.parse)(text3, { maxAliasCount: MAX_YAML_ALIASES, schema: "core", logLevel: "error", uniqueKeys: false, prettyErrors: false });
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 [{ item, message: `Its ${kind === "pre" ? "pre-request" : "post-response"} script uses ${names}, which Wayfarer does not have. The script will end in an error there.` }];
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("The cURL command does not make a request the app can hold.", issues);
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 { name: `${content.method} ${address2 ? address2.host + address2.pathname : parsed2.request.url}`, content, warnings };
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: { id: top.id, meta: top, name, order: 0, variables: [], auth: { type: "none" }, scripts: { pre: "", post: "" } },
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(({ name: requestName, warnings }) => warnings.map((message) => ({ item: `request "${requestName}"`, message })))
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(`The file does not make requests the app can hold.`, errors ?? []);
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")) throw new ImportError("This is a workspace backup, which replaces everything. Restore it in Settings.");
12877
- if (isPostmanV1(value)) throw new ImportError("This is a Postman Collection v1 file, which Wayfarer does not read. In Postman, export the collection as Collection v2.1 and import that.");
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 }) => ({ name, ...curlRequest({ 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 }, [...source.warnings, ...warnings, ...inheritedScriptWarnings(collection.payload)]),
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({ collections: collections.length, folders: count((payload2) => payload2.folders), requests: count((payload2) => payload2.requests), environments: environments.length }, [
12909
- ...warnings,
12910
- ...read.flatMap((collection) => collection.warnings)
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) throw new ImportError("The file is not a valid Wayfarer environments file.", (result.errors ?? []).map((message) => ({ path: "", message })));
12925
- return { report: report({ environments: result.payload.length }, []), collections: [], environments: result.payload };
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) throw new ImportError("The file is not a valid Wayfarer collection.", errors ?? []);
12929
- return { report: report({ collections: 1, folders: payload.folders.length, requests: payload.requests.length }, inheritedScriptWarnings(payload)), collections: [{ payload, plan }], environments: [] };
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 { $id: RUN_REPORT_FORMAT, ...info, iterations: report.iterations, ended: report.ended, ...report.reason !== void 0 && { reason: report.reason }, totals: counts(report.results), results: report.results };
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(/[&<>"\t\n\r]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", " ": "&#9;", "\n": "&#10;", "\r": "&#13;" })[char] ?? char);
14053
+ return text3.replace(/[^\t\n\r -퟿-�\u{10000}-\u{10FFFF}]/gu, "\uFFFD").replace(
14054
+ /[&<>"\t\n\r]/g,
14055
+ (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", " ": "&#9;", "\n": "&#10;", "\r": "&#13;" })[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) cases.push(` <testcase name="${classname}" classname="${classname}">
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
- if (result.skipped) cases.push(` <testcase name="${classname}" classname="${classname}">
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) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[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(await engine(), script, { ...extras, environment: env, ...response && { response: scriptResponse(response) } }, SCRIPT_LIMITS, {
13024
- libraries: await loadLibraries(script, loadLibrary, libraries),
13025
- ...send2 && { send: send2 }
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(environment2 && (environment2 = { ...environment2, vars: applyVariableChanges(environment2.vars, changes) }))
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 = () => ({ environment: environment2?.vars, folder, collection: collectionVariables, global: globals, data: iteration.data });
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) throw new SendBlockedError("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.");
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(new SendBlockedError(`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.`)),
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: { url: doc.url, scopes: { ...baseline, folder, data: iteration.data }, accepted: acceptedHosts }
14315
+ composed: {
14316
+ url: doc.url,
14317
+ scopes: { ...baseline, folder, data: iteration.data },
14318
+ accepted: acceptedHosts
14319
+ }
13087
14320
  }),
13088
- info: { requestName: doc.name, requestId: doc.id },
13089
- collection: { variables: () => collectionVariables, change: (changes) => Promise.resolve(collectionVariables = applyVariableChanges(collectionVariables, changes)) },
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 cli).
13131
- json writes wayfarer-run.json and junit writes
13132
- wayfarer-run.junit.xml, in --out.
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 (default: no
13136
- limit).
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
- or a request got no response; 2 nothing was run (see the message).`;
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 readText(file, what) {
14397
+ async function readText2(file, what) {
13152
14398
  try {
13153
14399
  return await readFile(file, "utf8");
13154
14400
  } catch (error) {
13155
- throw new Refusal(`The ${what} ${file} could not be read: ${error instanceof Error ? error.message : String(error)}`);
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) throw new Refusal(`${file} has no ${what} named "${name}". It has: ${list4.map((entry) => `"${entry.name}"`).join(", ")}.`);
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) throw new Refusal(`${file} holds ${list4.length} ${what}s: name one with --${what} (${list4.map((entry) => `"${entry.name}"`).join(", ")}).`);
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) throw new Refusal(`${file} holds a vault: set WAYFARER_VAULT_PASSPHRASE to its passphrase.`);
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 readText(file, "file");
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 { source: pick(collections.map(({ payload }) => ({ name: payload.collection.name, payload })), collection, "collection", file).payload, globals: [] };
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) throw new Refusal(`${file} is not a workspace backup this version reads:
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 && { vault: await openVault(vault, stores.secrets, file) }
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 readText(file, "environment"));
13216
- const { name, vars } = pick(environments, void 0, "environment", file);
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 readText(file, "vault file"));
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) throw new Refusal(`${file} is not a vault file ("$id": "${VAULT_FILE_FORMAT}"): ${issues.map((issue) => `${issue.path} ${issue.message}`.trim()).join("; ")}`);
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 error = result.error !== void 0 ? `
13244
- ${result.error}` : "";
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 tests2 = report.results.flatMap((result) => result.tests);
13249
- const failed = report.results.filter(runFailed).length;
13250
- const ended = { completed: "", cancelled: " The run was cancelled.", failed: " The run stopped at the first failure.", ended: ` The run ended: ${report.reason ?? ""}` }[report.ended];
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.2");
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) throw new Refusal(`Unknown command or arguments: ${positionals.join(" ")}
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) if (!["cli", "json", "junit"].includes(name)) throw new Refusal(`Unknown reporter "${name}": choose from cli, json and junit.`);
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(`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.`);
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
- const environment2 = values.environment === void 0 ? read.environment : await readEnvironment(values.environment);
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 readText(values.data, "data file"), basename(values.data));
14586
+ data = parseDataFile(await readText2(values.data, "data file"), basename(values.data));
13306
14587
  } catch (error) {
13307
- if (error instanceof Error && error.name === "DataFileError") throw new Refusal(`${values.data}: ${error.message}`);
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
- { source: read.source, ...environment2 && { environment: environment2 }, globals: read.globals, ...vault && { vault }, allowScripts: values["allow-scripts"], timeoutMs },
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
- if (reporters.has("json")) await writeFile(join(values.out, "wayfarer-run.json"), `${JSON.stringify(runReportJson(report, info), null, 2)}
13333
- `);
13334
- if (reporters.has("junit")) await writeFile(join(values.out, "wayfarer-run.junit.xml"), junitXml(report, info));
13335
- return report.results.some(runFailed) ? 1 : 0;
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,