wayfarer-cli 2.0.1 → 2.1.0

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