wayfarer-cli 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,8 +8928,12 @@ async function withDeadline(options, run) {
8716
8928
  return await run(controller.signal, () => clearTimeout(timer));
8717
8929
  } catch (cause) {
8718
8930
  if (cause instanceof TransportError) throw cause;
8719
- if (timedOut) throw new TransportError("timeout", `Timed out after ${options.timeoutMs} ms`);
8720
- if (options.signal.aborted) throw new TransportError("aborted", "The request was cancelled.");
8931
+ if (timedOut)
8932
+ throw new TransportError(
8933
+ "timeout",
8934
+ `No response within ${formatDuration(options.timeoutMs)}, the request timeout.`
8935
+ );
8936
+ if (options.signal.aborted) throw new TransportError("aborted", "You stopped the request.");
8721
8937
  throw new TransportError("network", networkMessage(cause));
8722
8938
  } finally {
8723
8939
  clearTimeout(timer);
@@ -8768,7 +8984,8 @@ async function readEventStream(response, signal, onEvent) {
8768
8984
  kept.set(chunk.value.subarray(0, room), size);
8769
8985
  size += Math.min(room, chunk.value.byteLength);
8770
8986
  if (chunk.value.byteLength > room) truncated = true;
8771
- for (const event of parser.push(decoder.decode(chunk.value, { stream: true }))) 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;
@@ -9974,7 +10191,8 @@ var isVmHash = (name) => Object.hasOwn(HASHES, name);
9974
10191
  var VM_ENCODINGS = ["utf8", "hex", "base64"];
9975
10192
  function fromWords(json) {
9976
10193
  const list4 = JSON.parse(json);
9977
- if (!Array.isArray(list4) || !list4.every((entry) => typeof entry === "number")) throw new TypeError("Expected a WordArray.");
10194
+ if (!Array.isArray(list4) || !list4.every((entry) => typeof entry === "number"))
10195
+ throw new TypeError("Expected a WordArray.");
9978
10196
  const [length, ...words] = list4;
9979
10197
  const bytes = new Uint8Array(Math.max(0, Math.min(length, words.length * 4)));
9980
10198
  for (let i = 0; i < bytes.length; i++) bytes[i] = words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
@@ -9998,7 +10216,10 @@ function vmHmac(name, keyFormat, key, format, payload) {
9998
10216
  }
9999
10217
  function vmEncode(encoding, text3) {
10000
10218
  if (encoding === "utf8") return toWords(new TextEncoder().encode(text3));
10001
- if (encoding === "hex") 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
+ );
10002
10223
  return toWords(Uint8Array.from(atob(text3.replace(/=+$/, "")), (char) => char.charCodeAt(0)));
10003
10224
  }
10004
10225
  function vmDecode(encoding, words) {
@@ -10014,7 +10235,9 @@ function vmDecode(encoding, words) {
10014
10235
  }
10015
10236
  function vmRandom(count) {
10016
10237
  if (!Number.isInteger(count) || count < 0 || count > 65536) throw new RangeError("Expected up to 65536 bytes.");
10017
- return Array.from(crypto.getRandomValues(new Uint8Array(count)), (byte) => byte.toString(16).padStart(2, "0")).join("");
10238
+ return Array.from(crypto.getRandomValues(new Uint8Array(count)), (byte) => byte.toString(16).padStart(2, "0")).join(
10239
+ ""
10240
+ );
10018
10241
  }
10019
10242
 
10020
10243
  // ../core/src/scripting/host.ts
@@ -10039,7 +10262,10 @@ var SCRIPT_LIMITS = {
10039
10262
  var WASM_PAGE = 65536;
10040
10263
  var ENGINE_INITIAL_PAGES = 256;
10041
10264
  function scriptMemory(limits = SCRIPT_LIMITS) {
10042
- return { 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
+ };
10043
10269
  }
10044
10270
  var LOG_TRUNCATED = "[console output truncated]";
10045
10271
  function emptyChanges() {
@@ -10054,9 +10280,30 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
10054
10280
  const scope = (rows2 = []) => new Map(rows2.map(([name, value]) => [name, value]));
10055
10281
  const marked = (rows2 = []) => new Set(rows2.filter(([, value, json]) => json === true && isJsonText(value)).map(([name]) => name));
10056
10282
  const state = {
10057
- values: { local: scope(), data: scope(context.data), environment: scope(context.environment), folder: scope(context.folder), collection: scope(context.collection), global: scope(context.globals) },
10058
- json: { local: marked(), data: marked(context.data), environment: marked(context.environment), folder: marked(context.folder), collection: marked(context.collection), global: marked(context.globals) },
10059
- changes: { local: /* @__PURE__ */ new Map(), data: /* @__PURE__ */ new Map(), environment: /* @__PURE__ */ new Map(), folder: /* @__PURE__ */ new Map(), collection: /* @__PURE__ */ new Map(), global: /* @__PURE__ */ new Map() },
10283
+ values: {
10284
+ local: scope(),
10285
+ data: scope(context.data),
10286
+ environment: scope(context.environment),
10287
+ folder: scope(context.folder),
10288
+ collection: scope(context.collection),
10289
+ global: scope(context.globals)
10290
+ },
10291
+ json: {
10292
+ local: marked(),
10293
+ data: marked(context.data),
10294
+ environment: marked(context.environment),
10295
+ folder: marked(context.folder),
10296
+ collection: marked(context.collection),
10297
+ global: marked(context.globals)
10298
+ },
10299
+ changes: {
10300
+ local: /* @__PURE__ */ new Map(),
10301
+ data: /* @__PURE__ */ new Map(),
10302
+ environment: /* @__PURE__ */ new Map(),
10303
+ folder: /* @__PURE__ */ new Map(),
10304
+ collection: /* @__PURE__ */ new Map(),
10305
+ global: /* @__PURE__ */ new Map()
10306
+ },
10060
10307
  sends: 0,
10061
10308
  inFlight: 0,
10062
10309
  answers: [],
@@ -10066,7 +10313,11 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
10066
10313
  tests: [],
10067
10314
  timers: /* @__PURE__ */ new Map()
10068
10315
  };
10069
- const changed = (name) => [...state.changes[name]].map(([key, value]) => ({ 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
+ }));
10070
10321
  const result = (error, limit) => ({
10071
10322
  logs: state.logs,
10072
10323
  changes: { environment: changed("environment"), collection: changed("collection"), global: changed("global") },
@@ -10086,7 +10337,8 @@ async function runScript(quickjs, source, context, limits = SCRIPT_LIMITS, servi
10086
10337
  try {
10087
10338
  failure = await evaluate(vm, runtime, source, context, state, deadline, limits, services);
10088
10339
  } catch (error) {
10089
- if (error instanceof RangeError || error instanceof Error && error.name === "InternalError") return result(void 0, "stack");
10340
+ if (error instanceof RangeError || error instanceof Error && error.name === "InternalError")
10341
+ return result(void 0, "stack");
10090
10342
  throw error;
10091
10343
  }
10092
10344
  vm.dispose();
@@ -10130,7 +10382,8 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
10130
10382
  };
10131
10383
  const charge = (...written) => {
10132
10384
  state.written += written.reduce((sum, part) => sum + part.length, 0);
10133
- if (state.written > limits.maxOutputBytes) 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.");
10134
10387
  };
10135
10388
  const scopeOf = (handle) => {
10136
10389
  const name = text3(handle);
@@ -10153,7 +10406,9 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
10153
10406
  });
10154
10407
  bind("varHas", (where, key) => lookUp(where, text3(key)) === void 0 ? vm.false : vm.true);
10155
10408
  bind("varAll", (where) => {
10156
- const of2 = (name) => [...state.values[name]].map(([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
+ );
10157
10412
  const entries = any(where) ? [...VARIABLE_ORDER].reverse().flatMap(of2) : of2(scopeOf(where));
10158
10413
  return vm.newString(`[${entries.join(",")}]`);
10159
10414
  });
@@ -10219,7 +10474,8 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
10219
10474
  const id = text3(name);
10220
10475
  const source2 = services.libraries?.get(id);
10221
10476
  if (source2 !== void 0) return vm.newString(source2);
10222
- if (VM_LIBRARIES.includes(id)) 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}')`);
10223
10479
  return void 0;
10224
10480
  });
10225
10481
  const hash = (handle) => {
@@ -10234,7 +10490,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
10234
10490
  return found;
10235
10491
  };
10236
10492
  bind("hash", (name, format, payload) => vm.newString(vmHash(hash(name), text3(format), text3(payload))));
10237
- bind("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
+ );
10238
10497
  bind("encode", (name, value) => vm.newString(vmEncode(encoding(name), text3(value))));
10239
10498
  bind("decode", (name, words) => vm.newString(vmDecode(encoding(name), text3(words))));
10240
10499
  bind("random", (count) => vm.newString(vmRandom(number(count))));
@@ -10258,7 +10517,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
10258
10517
  state.answers.push([call, ok, JSON.stringify(value)]);
10259
10518
  state.wake?.();
10260
10519
  };
10261
- const refused = (error) => ({ 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
+ });
10262
10524
  bind("sendFailed", (call, name, message) => {
10263
10525
  answer(number(call), false, { name: text3(name), message: text3(message) });
10264
10526
  return void 0;
@@ -10267,7 +10529,10 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
10267
10529
  const [id, body3] = [number(call), text3(json)];
10268
10530
  state.sends++;
10269
10531
  if (state.sends > limits.maxSendRequests) {
10270
- answer(id, false, { 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
+ });
10271
10536
  return void 0;
10272
10537
  }
10273
10538
  const send = services.send;
@@ -10370,12 +10635,15 @@ async function evaluate(vm, runtime, source, context, state, deadline, limits, s
10370
10635
  }
10371
10636
  }
10372
10637
  var VARIABLE_ORDER = VARIABLE_SCOPES;
10373
- var isPairs = (value) => Array.isArray(value) && value.every((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
+ );
10374
10641
  function parsed(json) {
10375
10642
  const value = JSON.parse(json);
10376
10643
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Expected a request.");
10377
10644
  const request = value;
10378
- if (typeof request["method"] !== "string" || !isHttpMethod(request["method"])) 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.");
10379
10647
  if (typeof request["url"] !== "string" || !request["url"].trim()) throw new TypeError("The request has no address.");
10380
10648
  if (!isPairs(request["headers"])) throw new TypeError("The request's headers are not a list of names and values.");
10381
10649
  return request;
@@ -10384,15 +10652,26 @@ function sendRequest(json) {
10384
10652
  const request = parsed(json);
10385
10653
  const body3 = request["body"];
10386
10654
  if (body3 !== void 0 && typeof body3 !== "string") throw new TypeError("The request's body is not text.");
10387
- return { 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
+ };
10388
10661
  }
10389
10662
  function scriptRequest(json) {
10390
10663
  const request = parsed(json);
10391
10664
  const body3 = request["body"];
10392
- 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
+ };
10393
10670
  if (body3?.mode === "raw" && typeof body3.raw === "string") return { ...base, body: { mode: "raw", raw: body3.raw } };
10394
- if (body3?.mode === "urlencoded" && isPairs(body3.urlencoded)) return { ...base, body: { mode: "urlencoded", urlencoded: body3.urlencoded } };
10395
- if (body3?.mode === "none" || body3?.mode === "multipart" || body3?.mode === "binary") return { ...base, body: { mode: body3.mode } };
10671
+ if (body3?.mode === "urlencoded" && isPairs(body3.urlencoded))
10672
+ return { ...base, body: { mode: "urlencoded", urlencoded: body3.urlencoded } };
10673
+ if (body3?.mode === "none" || body3?.mode === "multipart" || body3?.mode === "binary")
10674
+ return { ...base, body: { mode: body3.mode } };
10396
10675
  throw new TypeError("The request's body is not one a script can set.");
10397
10676
  }
10398
10677
  function property(vm, object, name) {
@@ -10414,7 +10693,14 @@ function scriptRequestOf(content) {
10414
10693
  function withScriptRequest(content, request) {
10415
10694
  const rows2 = (pairs2) => pairs2.map(([key, value]) => ({ key, value, enabled: true }));
10416
10695
  const untouched = content.body.mode === "graphql" && request.body.mode === "raw" && request.body.raw === graphqlText(content.body.graphql?.query ?? "", content.body.graphql?.variables ?? "");
10417
- const body3 = untouched ? content.body : request.body.mode === "raw" ? { ...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;
10418
10704
  return { ...content, method: request.method, url: request.url, headers: rows2(request.headers), body: body3 };
10419
10705
  }
10420
10706
  function sentScriptRequest(content, sent) {
@@ -10457,7 +10743,12 @@ async function runRequests(requests, send, options = {}) {
10457
10743
  const count = options.iterations ?? (data.length || 1);
10458
10744
  const signal = options.signal ?? new AbortController().signal;
10459
10745
  const results = [];
10460
- const report = (ended, reason) => ({ 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
+ });
10461
10752
  let first = true;
10462
10753
  for (let index = 0; index < count; index++) {
10463
10754
  const iteration = { index, count, data: data[Math.min(index, data.length - 1)] ?? [] };
@@ -10479,6 +10770,35 @@ async function runRequests(requests, send, options = {}) {
10479
10770
  return report("completed");
10480
10771
  }
10481
10772
 
10773
+ // ../core/src/run/words.ts
10774
+ function runEnding(report) {
10775
+ switch (report.ended) {
10776
+ case "completed":
10777
+ return "The run is complete.";
10778
+ case "cancelled":
10779
+ return "You stopped the run.";
10780
+ case "failed":
10781
+ return "The run stopped at the first failure.";
10782
+ case "ended":
10783
+ return `The run was stopped. ${report.reason ?? ""}`.trim();
10784
+ }
10785
+ }
10786
+ function runCounts(report) {
10787
+ const { results, iterations } = report;
10788
+ const tests2 = results.flatMap((result) => result.tests);
10789
+ const failed = results.filter(runFailed).length;
10790
+ const passed = tests2.filter((test) => test.passed).length;
10791
+ return `${results.length} ${results.length === 1 ? "result" : "results"} in ${iterations} ${iterations === 1 ? "iteration" : "iterations"}; ${failed} failed; ${passed} of ${tests2.length} tests passed.`;
10792
+ }
10793
+ function sharedErrors(results) {
10794
+ const counts2 = /* @__PURE__ */ new Map();
10795
+ for (const { error } of results) if (error !== void 0) counts2.set(error, (counts2.get(error) ?? 0) + 1);
10796
+ return [...counts2].filter(([, count]) => count >= 2).sort((a, b) => b[1] - a[1]).map(([error, count]) => ({ error, count }));
10797
+ }
10798
+ function sharedErrorHeading(count) {
10799
+ return `${count} requests failed with the same error.`;
10800
+ }
10801
+
10482
10802
  // ../core/src/send/wire.ts
10483
10803
  var SECRET_PLACEHOLDER = /\{\{\s*\$secret\./;
10484
10804
  function containsSecretPlaceholder(request) {
@@ -10682,14 +11002,17 @@ var RequestSkipped = class extends SendBlockedError {
10682
11002
  scriptLogs;
10683
11003
  nextRequest;
10684
11004
  };
10685
- var NETWORK_ERROR_TEXT = "Network error \u2014 no response was received. The host may not resolve (DNS), may have refused the connection, or may not allow cross-origin requests from this site (CORS). The browser does not reveal which. Check the URL and your connection; for CORS, the Local Bridge can relay the request.";
10686
- var networkErrorWithoutPage = (reason) => `Network error \u2014 no response was received: ${reason}. Check the URL, that the host resolves, and that it accepts connections.`;
11005
+ var NO_RESPONSE_TEXT = "The request got no response. The browser does not say why: the name may not resolve, the server may have refused the connection, or it does not allow requests from this page (CORS). Send it through the Local Bridge to see which.";
11006
+ var OFFLINE_TEXT = "The request got no response: this browser is offline. Connect, then send it again.";
11007
+ var noResponseWithoutPage = (reason) => `The request got no response: ${reason}. Check the address, that the name resolves and that the server accepts connections.`;
10687
11008
  var SECRET_PLACEHOLDER_BLOCKED = "This request refers to a vault secret that could not be read (it may have been deleted), so it was not sent. Give the variable a value again.";
10688
11009
  function sentBodyPreview(body3) {
10689
11010
  if (body3 === void 0 || typeof body3 === "string") return body3;
10690
11011
  const file = (blob) => `(${blob.size} bytes${blob.type ? `, ${blob.type}` : ""})`;
10691
11012
  if (body3 instanceof FormData) {
10692
- return [...body3].map(([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");
10693
11016
  }
10694
11017
  return `@file ${file(body3)}`;
10695
11018
  }
@@ -10710,7 +11033,10 @@ function bodyText(body3) {
10710
11033
  return stringifyJson(body3) ?? "";
10711
11034
  }
10712
11035
  function lastNext(runs) {
10713
- return runs.reduce((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
+ );
10714
11040
  }
10715
11041
  function scriptsToRun(spec, kind) {
10716
11042
  const label = kind === "pre" ? "Pre-request script" : "Post-response script";
@@ -10722,7 +11048,11 @@ function scriptsToRun(spec, kind) {
10722
11048
  function scriptRows(result, name, redactor) {
10723
11049
  if (!result) return [];
10724
11050
  const rows2 = result.error === void 0 ? result.testResults : [...result.testResults, { label: name, passed: false, error: result.error, source: "script" }];
10725
- return rows2.map((row) => ({ ...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
+ }));
10726
11056
  }
10727
11057
  async function exchange(spec, ports) {
10728
11058
  const createdAt = Date.now();
@@ -10733,7 +11063,11 @@ async function exchange(spec, ports) {
10733
11063
  let undo = () => Promise.resolve();
10734
11064
  const preScripts = spec.runScripts ? scriptsToRun(spec, "pre") : [];
10735
11065
  if (preScripts.length) {
10736
- const before = { 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
+ };
10737
11071
  const touched = emptyChanges();
10738
11072
  const restoreSecrets = [];
10739
11073
  const was = (rows2, names) => names.map(({ key }) => {
@@ -10741,13 +11075,27 @@ async function exchange(spec, ports) {
10741
11075
  return { key, value, ...json && { json } };
10742
11076
  });
10743
11077
  undo = async () => {
10744
- await applyChanges(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
+ );
10745
11088
  for (const restore of restoreSecrets.reverse()) await restore();
10746
11089
  };
10747
11090
  timings.preScriptMs = 0;
10748
11091
  for (const { name, text: text3 } of preScripts) {
10749
11092
  const scriptStarted = performance.now();
10750
- const result = await ports.runScript(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
+ );
10751
11099
  timings.preScriptMs += Math.round(performance.now() - scriptStarted);
10752
11100
  preRuns.push({ name, result });
10753
11101
  const { changes } = result;
@@ -10755,13 +11103,22 @@ async function exchange(spec, ports) {
10755
11103
  for (const scope of ["environment", "global", "collection"]) touched[scope].push(...changes[scope]);
10756
11104
  if (result.error !== void 0) {
10757
11105
  const nothing = new Redactor([]);
10758
- throw new PreRequestScriptError(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
+ );
10759
11112
  }
10760
11113
  if (result.request) template = withScriptRequest(template, result.request);
10761
11114
  }
10762
11115
  if (spec.iteration && preRuns.some((run) => run.result.skipRequest)) {
10763
11116
  const nothing = new Redactor([]);
10764
- throw new RequestSkipped(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
+ );
10765
11122
  }
10766
11123
  }
10767
11124
  let request;
@@ -10782,14 +11139,19 @@ async function exchange(spec, ports) {
10782
11139
  const jar = ports.cookies;
10783
11140
  const cookies = jar && {
10784
11141
  for: (url) => {
10785
- const found = jar.for(url).filter((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
+ );
10786
11145
  sentCookies ??= found;
10787
11146
  for (const cookie of found) cookieValues.add(cookie.value);
10788
11147
  return found;
10789
11148
  },
10790
11149
  store: (url, setCookies) => void jar.store(url, setCookies, request.secrets)
10791
11150
  };
10792
- const policy = { 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
+ };
10793
11155
  const transmit = async () => {
10794
11156
  startedAt = performance.now();
10795
11157
  sentCookies = void 0;
@@ -10830,7 +11192,7 @@ async function exchange(spec, ports) {
10830
11192
  const durationMs = Math.round(performance.now() - startedAt);
10831
11193
  timings.requestMs = durationMs;
10832
11194
  ports.watch?.received(request.url);
10833
- const shaped = outcome instanceof TransportError ? shapeFailure(outcome, request.url, ports.page) : shapeResponse(outcome, request.url, ports.page);
11195
+ const shaped = outcome instanceof TransportError ? shapeFailure(outcome, ports.page, ports.online?.()) : shapeResponse(outcome, ports.page);
10834
11196
  const redactor = new Redactor([...request.secrets, ...request.credentials, ...cookieValues]);
10835
11197
  const secrets = new Redactor(request.secrets);
10836
11198
  const post = await runPostScriptAndAssertions(
@@ -10848,7 +11210,14 @@ async function exchange(spec, ports) {
10848
11210
  notes,
10849
11211
  secrets,
10850
11212
  timings,
10851
- { 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
+ }
10852
11221
  );
10853
11222
  const runs = [...preRuns, ...post.scripts];
10854
11223
  const testResults = [...runs.flatMap((run) => scriptRows(run.result, run.name, redactor)), ...post.assertions];
@@ -10868,7 +11237,10 @@ async function exchange(spec, ports) {
10868
11237
  ...preview !== void 0 && { bodyPreview: keep(preview).text }
10869
11238
  },
10870
11239
  route: outcome instanceof TransportError ? policy.took ?? ports.transport.first(policy.route) : outcome.route,
10871
- durationMs
11240
+ durationMs,
11241
+ ...spec.info && {
11242
+ sentFrom: { name: redactor.text(spec.info.requestName), path: spec.info.path.map((name) => redactor.text(name)) }
11243
+ }
10872
11244
  };
10873
11245
  if (!(outcome instanceof TransportError)) {
10874
11246
  history.response = {
@@ -10882,7 +11254,25 @@ async function exchange(spec, ports) {
10882
11254
  history.error = redactor.text(shaped.historyError);
10883
11255
  }
10884
11256
  const nextRequest = lastNext(runs);
11257
+ const record = recordOf(history, {
11258
+ sentHeaders: withJarCookies(request.headers, sentCookies ?? []),
11259
+ sentUrl: request.url,
11260
+ sentPreview: preview,
11261
+ received: outcome instanceof TransportError ? void 0 : { headers: shaped.headerList, body: shaped.bodyText },
11262
+ vaulted: (request.vaulted ?? []).map((secret) => ({
11263
+ ...secret,
11264
+ ...secret.scope && { holder: holderOf(secret.scope, ports, spec) }
11265
+ })),
11266
+ unpaired: outcome instanceof TransportError && history.route === "direct" && policy.unpaired === true,
11267
+ failure: outcome instanceof TransportError ? {
11268
+ kind: outcome.kind,
11269
+ reported: redactor.text(outcome.message),
11270
+ summary: redactor.text(firstSentence(shaped.response.errorText))
11271
+ } : void 0
11272
+ });
11273
+ history.record = record;
10885
11274
  return {
11275
+ record,
10886
11276
  durationMs,
10887
11277
  timings,
10888
11278
  ...!(outcome instanceof TransportError) && outcome.timings && { relayTimings: outcome.timings },
@@ -10894,11 +11284,44 @@ async function exchange(spec, ports) {
10894
11284
  ...nextRequest !== void 0 && { nextRequest }
10895
11285
  };
10896
11286
  }
10897
- function 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) {
10898
11322
  const body3 = decodeEnvelope(envelope);
10899
11323
  const isError = envelope.status < 200 || envelope.status >= 300;
10900
- const failureText = `Http failure response for ${url}: ${envelope.status} ${envelope.statusText}`;
10901
- const shown = isError ? body3 ?? failureText : body3;
11324
+ const shown = body3;
10902
11325
  const bodyIsJson = isJsonPayload(shown);
10903
11326
  const text3 = bodyIsJson ? serializeJsonPayload(shown) : stringifyPayload(shown);
10904
11327
  const header = (name) => envelope.headers.find(([key]) => key.toLowerCase() === name)?.[1];
@@ -10908,7 +11331,6 @@ function shapeResponse(envelope, url, page) {
10908
11331
  headers: Object.fromEntries(envelope.headers),
10909
11332
  headerList: envelope.headers,
10910
11333
  bodyText: body3 === void 0 || body3 === null || body3 instanceof BinaryBody ? void 0 : text3,
10911
- historyError: isError ? failureText : void 0,
10912
11334
  response: {
10913
11335
  isError,
10914
11336
  statusCode: envelope.status,
@@ -10917,34 +11339,36 @@ function shapeResponse(envelope, url, page) {
10917
11339
  dataText: isError ? "" : text3,
10918
11340
  errorText: isError ? text3 : "",
10919
11341
  headersView: envelope.headers.map(([name, value]) => ({ name, value })).sort((a, b) => a.name.localeCompare(b.name)),
10920
- contentLength: Number.isFinite(contentLength) ? contentLength : void 0,
11342
+ contentLength: Number.isFinite(contentLength) ? contentLength : envelope.sizes.decoded,
10921
11343
  binary: body3 instanceof BinaryBody ? body3 : void 0,
10922
11344
  redirectedTo: envelope.redirected ? envelope.finalUrl : void 0,
10923
11345
  route: envelope.route,
10924
11346
  contentType: header("content-type"),
10925
- // Outside a page nothing is withheld: no browser stands between.
10926
- 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() === "*"),
10927
11350
  ...envelope.stream && { stream: envelope.stream },
10928
11351
  ...envelope.redirects && { redirects: envelope.redirects },
10929
11352
  ...envelope.tlsInsecure && { tlsInsecure: true }
10930
11353
  }
10931
11354
  };
10932
11355
  }
10933
- function shapeFailure(failure, url, page) {
11356
+ function shapeFailure(failure, page, online) {
10934
11357
  const network = failure.kind === "network";
10935
- 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;
10936
11360
  return {
10937
- body: network ? void 0 : failure.message,
11361
+ body: said2 ? void 0 : failure.message,
10938
11362
  headers: {},
10939
11363
  headerList: [],
10940
- 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,
10941
11365
  response: {
10942
11366
  isError: true,
10943
- statusCode,
10944
- statusText: network ? "Unknown Error" : "",
11367
+ statusCode: void 0,
11368
+ statusText: "",
10945
11369
  bodyIsJson: false,
10946
11370
  dataText: "",
10947
- errorText: !network ? failure.message : page === void 0 ? networkErrorWithoutPage(failure.message) : NETWORK_ERROR_TEXT,
11371
+ errorText,
10948
11372
  headersView: []
10949
11373
  }
10950
11374
  };
@@ -10953,13 +11377,21 @@ async function runPostScriptAndAssertions(ports, spec, sent, notes, variables2,
10953
11377
  const scripts2 = [];
10954
11378
  for (const { name, text: text3 } of spec.runScripts ? scriptsToRun(spec, "post") : []) {
10955
11379
  const scriptStarted = performance.now();
10956
- const result = await ports.runScript(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
+ );
10957
11386
  timings.postScriptMs = (timings.postScriptMs ?? 0) + Math.round(performance.now() - scriptStarted);
10958
11387
  scripts2.push({ name, result });
10959
11388
  await applyChanges(ports, spec, result.changes, notes, variables2);
10960
11389
  }
10961
11390
  const { statusCode, body: body3, headers: headers2, durationMs } = response;
10962
- return { 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
+ };
10963
11395
  }
10964
11396
  function environmentSnapshot(ports) {
10965
11397
  return scriptVariables(ports.environment()?.vars ?? []);
@@ -10972,17 +11404,31 @@ function scriptExtras(ports, spec, eventName, request) {
10972
11404
  ...spec.iteration && { data: scriptVariables(spec.iteration.data) },
10973
11405
  collection: scriptVariables(spec.collection?.variables() ?? []),
10974
11406
  request,
10975
- info: { 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
+ },
10976
11413
  send: (request2) => sendForScript(ports, request2, spec.signal, spec.route)
10977
11414
  };
10978
11415
  }
10979
11416
  async function sendForScript(ports, request, signal, route = ports.transport.route()) {
10980
11417
  if (containsSecretPlaceholder(request)) {
10981
- throw Object.assign(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
+ );
10982
11424
  }
10983
11425
  const startedAt = performance.now();
10984
- const envelope = await ports.transport.send(request, { signal: signal ?? new AbortController().signal, timeoutMs: ports.timeoutMs() }, { route });
10985
- const shaped = shapeResponse(envelope, request.url, ports.page);
11426
+ const envelope = await ports.transport.send(
11427
+ request,
11428
+ { signal: signal ?? new AbortController().signal, timeoutMs: ports.timeoutMs() },
11429
+ { route }
11430
+ );
11431
+ const shaped = shapeResponse(envelope, ports.page);
10986
11432
  return scriptResponse({
10987
11433
  statusCode: envelope.status,
10988
11434
  statusText: envelope.statusText,
@@ -10995,7 +11441,12 @@ async function sendForScript(ports, request, signal, route = ports.transport.rou
10995
11441
  async function applyChanges(ports, spec, changes, notes, secrets) {
10996
11442
  const active = ports.environment();
10997
11443
  const replaced = /* @__PURE__ */ new Map();
10998
- const kept = async (api, rows2, scope) => (await intoVault(ports, api, rows2, scope, notes, replaced)).map(({ key, value, json }) => ({ 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
+ }));
10999
11450
  const environment2 = await kept("pm.environment", active?.vars ?? [], changes.environment);
11000
11451
  const global = await kept("pm.globals", ports.globals(), changes.global);
11001
11452
  const collection = await kept("pm.collectionVariables", spec.collection?.variables() ?? [], changes.collection);
@@ -11006,7 +11457,10 @@ async function applyChanges(ports, spec, changes, notes, secrets) {
11006
11457
  if (global.length) await ports.changeGlobals(global);
11007
11458
  if (collection.length) {
11008
11459
  if (spec.collection) await spec.collection.change(collection);
11009
- else 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
+ );
11010
11464
  }
11011
11465
  return async () => {
11012
11466
  for (const [id, plaintext] of replaced) await ports.vault.replaceSecret(id, plaintext);
@@ -11026,7 +11480,8 @@ async function intoVault(ports, api, rows2, changes, notes, replaced) {
11026
11480
  continue;
11027
11481
  }
11028
11482
  const old = await ports.vault.readSecret(id);
11029
- if (old === null || !await ports.vault.replaceSecret(id, change.value)) 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");
11030
11485
  else if (!replaced.has(id)) replaced.set(id, old);
11031
11486
  }
11032
11487
  }
@@ -11069,7 +11524,9 @@ function stringifyPayload(payload) {
11069
11524
  // ../core/src/send/build.ts
11070
11525
  var UnresolvedVariablesError = class extends SendBlockedError {
11071
11526
  constructor(names) {
11072
- super(`${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
+ );
11073
11530
  this.names = names;
11074
11531
  }
11075
11532
  names;
@@ -11151,7 +11608,8 @@ function buildRequest(content, context) {
11151
11608
  const to = hostOf(endpointText, scopes());
11152
11609
  if (from === void 0 || from === to || composed?.accepted?.has(to)) return withSecrets();
11153
11610
  return (context.askHost?.(from, to) ?? Promise.resolve(false)).then((accepted) => {
11154
- if (!accepted) 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.");
11155
11613
  composed?.accepted?.add(to);
11156
11614
  return withSecrets();
11157
11615
  });
@@ -11161,11 +11619,17 @@ function build(content, resolver, context) {
11161
11619
  const auth3 = resolveAuth(sentAuth(content.auth, context.ancestors), resolve);
11162
11620
  const withoutValue = [...resolver.unresolved];
11163
11621
  const sent = isBodyMethod(content.method) ? resolveBody(content.body, resolve, context) : void 0;
11164
- const finish = (body3, token2) => sign(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
+ );
11165
11626
  const oauth = auth3.type === "oauth2" ? oauthOf(content.auth, context.ancestors, context.scopes()) : null;
11166
- if (auth3.type !== "oauth2" || !oauth) return sent instanceof Promise ? sent.then((body3) => finish(body3)) : finish(sent);
11627
+ if (auth3.type !== "oauth2" || !oauth)
11628
+ return sent instanceof Promise ? sent.then((body3) => finish(body3)) : finish(sent);
11167
11629
  if (withoutValue.length) {
11168
- throw new SendBlockedError(`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
+ );
11169
11633
  }
11170
11634
  if (!context.oauthToken) throw new SendBlockedError("OAuth 2.0 tokens are not fetched here. Nothing was sent.");
11171
11635
  const token = context.oauthToken(auth3, oauth.key, context.renewToken ?? false).catch((error) => {
@@ -11176,18 +11640,30 @@ function build(content, resolver, context) {
11176
11640
  function sign(built2, auth3) {
11177
11641
  if (auth3.type !== "awsv4" || !URL.canParse(built2.url)) return built2;
11178
11642
  if (built2.body instanceof FormData) {
11179
- throw new SendBlockedError("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
+ );
11180
11646
  }
11181
- if (!globalThis.crypto?.subtle) throw new SendBlockedError("AWS Signature needs the browser's cryptography, which a page has only over https or from localhost. Nothing was sent.");
11647
+ if (!globalThis.crypto?.subtle)
11648
+ throw new SendBlockedError(
11649
+ "AWS Signature needs the browser's cryptography, which a page has only over https or from localhost. Nothing was sent."
11650
+ );
11182
11651
  const add = async (body3) => {
11183
11652
  const own3 = built2.headers.filter(([name]) => /^(content-type$|x-amz-)/i.test(name));
11184
- const { headers: signature } = await signV4({ 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
+ );
11185
11658
  const replaced = new Set(signature.map(([name]) => name.toLowerCase()));
11186
11659
  return {
11187
11660
  ...built2,
11188
11661
  headers: [...built2.headers.filter(([name]) => !replaced.has(name.toLowerCase())), ...signature],
11189
11662
  // The signature's header too: a server may send it back.
11190
- credentials: [...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
+ ]
11191
11667
  };
11192
11668
  };
11193
11669
  if (built2.body instanceof Blob) return built2.body.arrayBuffer().then((bytes) => add(new Uint8Array(bytes)));
@@ -11219,8 +11695,16 @@ function assemble(content, sent, resolver, extra) {
11219
11695
  body: sent?.data,
11220
11696
  // An OAuth 2.0 token is kept like a vault secret and masked like one: in history, in every export, and in what a script is shown.
11221
11697
  secrets: [...resolver.taint, ...extra.token ? [extra.token] : []],
11698
+ // By name only, for the exchange record (D4.12): the variables that brought a vault secret in, and the token.
11699
+ vaulted: [
11700
+ ...[...resolver.vaulted].map(([name, scope]) => ({ name, ...scope && { scope } })),
11701
+ ...extra.token ? [{ name: "OAuth 2.0 token" }] : []
11702
+ ],
11222
11703
  // Also what a credential header carries, typed by hand or built from the Auth tab: a server may send it back.
11223
- credentials: [...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)
11224
11708
  };
11225
11709
  }
11226
11710
  function resolveBody(body3, resolve, context) {
@@ -11241,7 +11725,11 @@ function resolveBody(body3, resolve, context) {
11241
11725
  return { data: new URLSearchParams(pairs2).toString(), contentType: "application/x-www-form-urlencoded" };
11242
11726
  }
11243
11727
  case "multipart":
11244
- return multipartForm((body3.multipart ?? []).filter((item) => item.enabled), resolve, context);
11728
+ return multipartForm(
11729
+ (body3.multipart ?? []).filter((item) => item.enabled),
11730
+ resolve,
11731
+ context
11732
+ );
11245
11733
  case "binary":
11246
11734
  return binaryFile(body3.binary, context);
11247
11735
  }
@@ -11284,10 +11772,19 @@ async function runOutcome(send, declined) {
11284
11772
  exchange: result.history
11285
11773
  };
11286
11774
  } catch (error) {
11287
- if (error instanceof RequestSkipped) 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
+ };
11288
11783
  if (error instanceof SendDeclinedError) return { outcome: { error: error.message, tests: [], end: declined } };
11289
- if (error instanceof PreRequestScriptError) return { outcome: { error: error.message, tests: tests(error.testResults) } };
11290
- if (error instanceof SendBlockedError || error instanceof VariableNestingError) return { outcome: { error: error.message, tests: [] } };
11784
+ if (error instanceof PreRequestScriptError)
11785
+ return { outcome: { error: error.message, tests: tests(error.testResults) } };
11786
+ if (error instanceof SendBlockedError || error instanceof VariableNestingError)
11787
+ return { outcome: { error: error.message, tests: [] } };
11291
11788
  throw error;
11292
11789
  }
11293
11790
  }
@@ -11314,7 +11811,14 @@ function validateWorkspaceFile(value) {
11314
11811
  }
11315
11812
  return records;
11316
11813
  };
11317
- const [collections, folders, requests, environments, secrets, meta] = ["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);
11318
11822
  if (errors.length) return errors;
11319
11823
  const idOf2 = (doc) => isObject2(doc["meta"]) ? doc["meta"]["id"] : void 0;
11320
11824
  const known = new Set(collections.map(idOf2));
@@ -11327,12 +11831,21 @@ function validateWorkspaceFile(value) {
11327
11831
  folders: folders.filter((folder) => folder["collectionId"] === id),
11328
11832
  requests: requests.filter((request) => request["collectionId"] === id)
11329
11833
  });
11330
- errors.push(...(result.errors ?? []).map((issue) => `collection "${String(collection["name"])}": ${issue.path}: ${issue.message}`));
11331
- if (!isObject2(collection["scriptTrust"])) errors.push(`collection "${String(collection["name"])}": scriptTrust is missing.`);
11834
+ errors.push(
11835
+ ...(result.errors ?? []).map(
11836
+ (issue) => `collection "${String(collection["name"])}": ${issue.path}: ${issue.message}`
11837
+ )
11838
+ );
11839
+ if (!isObject2(collection["scriptTrust"]))
11840
+ errors.push(`collection "${String(collection["name"])}": scriptTrust is missing.`);
11332
11841
  }
11333
- for (const [kind, docs] of [["folders", folders], ["requests", requests]]) {
11842
+ for (const [kind, docs] of [
11843
+ ["folders", folders],
11844
+ ["requests", requests]
11845
+ ]) {
11334
11846
  docs.forEach((doc, index) => {
11335
- if (!known.has(doc["collectionId"])) 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.`);
11336
11849
  });
11337
11850
  }
11338
11851
  errors.push(...validateEnvironmentExport({ $id: ENVIRONMENTS_FORMAT, environments }).errors ?? []);
@@ -11341,9 +11854,14 @@ function validateWorkspaceFile(value) {
11341
11854
  const key = record["key"];
11342
11855
  if (key === META_STATE_KEY) {
11343
11856
  const active = record["activeEnvironmentId"];
11344
- if (active !== null && active !== void 0 && typeof active !== "string") 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.");
11345
11859
  } else if (key === META_GLOBALS_KEY) {
11346
- 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
+ );
11347
11865
  } else if (key === META_VAULT_KEY) {
11348
11866
  vault = record;
11349
11867
  } else {
@@ -11354,7 +11872,8 @@ function validateWorkspaceFile(value) {
11354
11872
  const issues = validateVaultFile({ $id: VAULT_FILE_FORMAT, vault, secrets });
11355
11873
  errors.push(...issues.map((issue) => `vault: ${issue.path}: ${issue.message}`));
11356
11874
  secrets.forEach((secret, index) => {
11357
- if (!isObject2(secret["meta"]) || secret["meta"]["id"] !== secret["id"]) 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.`);
11358
11877
  });
11359
11878
  }
11360
11879
  const history = own(stores, "history");
@@ -11363,16 +11882,47 @@ function validateWorkspaceFile(value) {
11363
11882
  const path = `stores.history[${index}]`;
11364
11883
  const sent = entry["sent"];
11365
11884
  if (typeof entry["createdAt"] !== "number") errors.push(`${path}.createdAt: Value must be a number.`);
11366
- if (entry["route"] !== "direct" && entry["route"] !== "bridge") 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.`);
11367
11887
  if (!isObject2(sent) || typeof sent["method"] !== "string" || typeof sent["url"] !== "string" || !Array.isArray(sent["headers"])) {
11368
11888
  errors.push(`${path}.sent: Value must hold a method, a URL and headers.`);
11369
11889
  }
11370
- errors.push(...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
+ }
11371
11908
  });
11372
11909
  }
11373
11910
  return errors;
11374
11911
  }
11375
11912
 
11913
+ // ../core/src/import/import-error.ts
11914
+ var ImportError = class extends Error {
11915
+ constructor(message, issues = [], cause) {
11916
+ super(message, { cause });
11917
+ this.issues = issues;
11918
+ }
11919
+ issues;
11920
+ name = "ImportError";
11921
+ };
11922
+
11923
+ // ../core/src/share/link.ts
11924
+ var SHARE_LINK_MAX_BYTES = 12 * 1024;
11925
+
11376
11926
  // ../core/src/run/data-file.ts
11377
11927
  var DataFileError = class extends Error {
11378
11928
  name = "DataFileError";
@@ -11414,28 +11964,37 @@ function parseDataFile(text3, fileName) {
11414
11964
  if (/\.json$/i.test(fileName)) {
11415
11965
  const parsed2 = parseJson(text3);
11416
11966
  const isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
11417
- if (!parsed2.ok || !Array.isArray(parsed2.value) || !parsed2.value.every(isObject5)) 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.");
11418
11969
  return parsed2.value.map(
11419
- (entry) => Object.entries(entry).map(([key, value]) => typeof value === "string" ? { key, value, enabled: true } : { key, value: stringifyJson(value) ?? "null", enabled: true, json: true })
11970
+ (entry) => Object.entries(entry).map(
11971
+ ([key, value]) => typeof value === "string" ? { key, value, enabled: true } : { key, value: stringifyJson(value) ?? "null", enabled: true, json: true }
11972
+ )
11420
11973
  );
11421
11974
  }
11422
11975
  const [names, ...records] = parseCsv(text3);
11423
- if (!names?.some((name) => name)) throw new DataFileError("A CSV data file has the names of its values in its first line.");
11424
- return records.filter((record) => record.length > 1 || record[0] !== "").map((record) => names.flatMap((key, index) => key ? [{ key, value: record[index] ?? "", enabled: true }] : []));
11976
+ if (!names?.some((name) => name))
11977
+ throw new DataFileError("A CSV data file has the names of its values in its first line.");
11978
+ return records.filter((record) => record.length > 1 || record[0] !== "").map(
11979
+ (record) => names.flatMap((key, index) => key ? [{ key, value: record[index] ?? "", enabled: true }] : [])
11980
+ );
11425
11981
  }
11426
11982
 
11427
- // ../core/src/import/import-error.ts
11428
- var ImportError = class extends Error {
11429
- constructor(message, issues = []) {
11430
- super(message);
11431
- this.issues = issues;
11432
- }
11433
- issues;
11434
- name = "ImportError";
11435
- };
11436
-
11437
11983
  // ../core/src/import/curl.ts
11438
- var ANSI_C = { 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
+ };
11439
11998
  function ansiEscape(text3, at) {
11440
11999
  const numbered = [
11441
12000
  [/^x([0-9a-fA-F]{1,2})/, 16],
@@ -11568,7 +12127,33 @@ var FLAGS = {
11568
12127
  "--insecure": "insecure",
11569
12128
  "--compressed": "compressed"
11570
12129
  };
11571
- var QUIET_FLAGS = /* @__PURE__ */ new Set(["-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
+ ]);
11572
12157
  var SKIPPED_VALUE_OPTIONS = /* @__PURE__ */ new Set([
11573
12158
  "-o",
11574
12159
  "--output",
@@ -11650,7 +12235,9 @@ function formPart(value, warnings) {
11650
12235
  if (quotedFile) return { name, fileName: quotedFile[1].replace(/\\(.)/g, "$1") };
11651
12236
  if (content.startsWith("@")) return { name, fileName: content.slice(1).split(/[;,]/)[0] };
11652
12237
  if (content.startsWith("<")) {
11653
- warnings.push(`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
+ );
11654
12241
  return { name, value: "" };
11655
12242
  }
11656
12243
  return { name, value: content.replace(/;type=[^;]*$/, "") };
@@ -11733,7 +12320,8 @@ function parseCurl(text3) {
11733
12320
  } else if (word.startsWith("--")) {
11734
12321
  if (Object.hasOwn(VALUE_OPTIONS, word)) take(VALUE_OPTIONS[word], valueOf(word));
11735
12322
  else if (Object.hasOwn(FLAGS, word)) ({ get, head } = flag(FLAGS[word], { get, head }, warnings));
11736
- else if (SKIPPED_VALUE_OPTIONS.has(word)) 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.`);
11737
12325
  else if (!QUIET_FLAGS.has(word)) warnings.push(`${word} was left out: the app does not know this option.`);
11738
12326
  } else {
11739
12327
  for (let at = 1; at < word.length; at++) {
@@ -11753,14 +12341,18 @@ function parseCurl(text3) {
11753
12341
  }
11754
12342
  }
11755
12343
  if (!urls.length) throw new ImportError("The cURL command has no address.");
11756
- if (urls.length > 1) 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
+ );
11757
12348
  let url = urls[0];
11758
12349
  const type = (contentType(headers2) ?? "").toLowerCase();
11759
12350
  let body3 = { mode: "none" };
11760
12351
  const files = data.flatMap((datum) => "file" in datum ? [datum.file] : []);
11761
12352
  if (parts.length) {
11762
12353
  body3 = { mode: "multipart", parts };
11763
- if (data.length) 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.");
11764
12356
  } else if (get) {
11765
12357
  const query = data.flatMap((datum) => "text" in datum ? [datum.text] : []).join("&");
11766
12358
  if (files.length) warnings.push(`The data of the file ${files.join(", ")} was left out: the file was not read.`);
@@ -11773,14 +12365,21 @@ function parseCurl(text3) {
11773
12365
  body3 = !type || type.startsWith("application/x-www-form-urlencoded") ? { mode: "urlencoded", fields: [...new URLSearchParams(wire)] } : { mode: "raw", text: wire };
11774
12366
  }
11775
12367
  method ??= head ? "HEAD" : body3.mode === "none" ? "GET" : "POST";
11776
- if (!isHttpMethod(method)) 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
+ );
11777
12372
  return { request: { method, url, headers: headers2, body: body3 }, ...basic && { basic }, warnings };
11778
12373
  }
11779
12374
  function flag(kind, state, warnings) {
11780
12375
  if (kind === "get") return { ...state, get: true };
11781
12376
  if (kind === "head") return { ...state, head: true };
11782
- if (kind === "insecure") warnings.push("-k (do not check the server's certificate) was left out: a browser always checks it, and the Local Bridge cannot skip the check yet.");
11783
- if (kind === "compressed") warnings.push("--compressed was left out: the browser asks for a compressed answer by itself.");
12377
+ if (kind === "insecure")
12378
+ warnings.push(
12379
+ "-k (do not check the server's certificate) was left out: a browser always checks it, and the Local Bridge cannot skip the check yet."
12380
+ );
12381
+ if (kind === "compressed")
12382
+ warnings.push("--compressed was left out: the browser asks for a compressed answer by itself.");
11784
12383
  return state;
11785
12384
  }
11786
12385
  function languageOf(type) {
@@ -11799,8 +12398,15 @@ function curlRequest(parsed2) {
11799
12398
  const language = languageOf(type.toLowerCase());
11800
12399
  const row = (key, value) => ({ key, value, enabled: true });
11801
12400
  const implied = body3.mode === "urlencoded" ? "application/x-www-form-urlencoded" : body3.mode === "multipart" ? "multipart/form-data" : body3.mode === "raw" ? RAW_CONTENT_TYPES[language] : null;
11802
- const headers2 = request.headers.filter(([name, value]) => !(name.toLowerCase() === "content-type" && implied !== null && value.toLowerCase().split(";")[0].trim() === implied));
11803
- const content = { ...emptyRequest(), method: request.method, url: request.url, headers: headers2.map(([name, value]) => row(name, value)) };
12401
+ const headers2 = request.headers.filter(
12402
+ ([name, value]) => !(name.toLowerCase() === "content-type" && implied !== null && value.toLowerCase().split(";")[0].trim() === implied)
12403
+ );
12404
+ const content = {
12405
+ ...emptyRequest(),
12406
+ method: request.method,
12407
+ url: request.url,
12408
+ headers: headers2.map(([name, value]) => row(name, value))
12409
+ };
11804
12410
  if (basic) content.auth = { type: "basic", ...basic };
11805
12411
  if (body3.mode === "raw") {
11806
12412
  content.body = { mode: "raw", raw: { language, text: body3.text } };
@@ -11809,12 +12415,23 @@ function curlRequest(parsed2) {
11809
12415
  } else if (body3.mode === "multipart") {
11810
12416
  content.body = {
11811
12417
  mode: "multipart",
11812
- multipart: body3.parts.map((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
+ )
11813
12421
  };
11814
- for (const part of body3.parts) if ("fileName" in part) warnings.push(`The form part ${part.name} sends the file ${part.fileName}. Choose the file again in the Body tab: a command names a file, it does not hold it.`);
12422
+ for (const part of body3.parts)
12423
+ if ("fileName" in part)
12424
+ warnings.push(
12425
+ `The form part ${part.name} sends the file ${part.fileName}. Choose the file again in the Body tab: a command names a file, it does not hold it.`
12426
+ );
11815
12427
  } else if (body3.mode === "binary") {
11816
- content.body = { mode: "binary", ...type && { binary: { fileId: "", fileName: body3.fileName, contentType: type } } };
11817
- warnings.push(`The body is the file ${body3.fileName}. Choose the file again in the Body tab: a command names a file, it does not hold it.`);
12428
+ content.body = {
12429
+ mode: "binary",
12430
+ ...type && { binary: { fileId: "", fileName: body3.fileName, contentType: type } }
12431
+ };
12432
+ warnings.push(
12433
+ `The body is the file ${body3.fileName}. Choose the file again in the Body tab: a command names a file, it does not hold it.`
12434
+ );
11818
12435
  }
11819
12436
  return { content, warnings };
11820
12437
  }
@@ -11835,16 +12452,23 @@ function bodyOf(postData, warnings) {
11835
12452
  const params = list(own2(postData, "params"));
11836
12453
  if (type.startsWith("multipart/form-data")) {
11837
12454
  if (!params.length) {
11838
- warnings.push("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
+ );
11839
12458
  return { mode: "raw", text: text(own2(postData, "text")) };
11840
12459
  }
11841
12460
  return {
11842
12461
  mode: "multipart",
11843
- parts: params.map((param) => typeof own2(param, "fileName") === "string" ? { name: text(own2(param, "name")), fileName: text(own2(param, "fileName")) } : { name: text(own2(param, "name")), value: text(own2(param, "value")) })
12462
+ parts: params.map(
12463
+ (param) => typeof own2(param, "fileName") === "string" ? { name: text(own2(param, "name")), fileName: text(own2(param, "fileName")) } : { name: text(own2(param, "name")), value: text(own2(param, "value")) }
12464
+ )
11844
12465
  };
11845
12466
  }
11846
12467
  if (type.startsWith("application/x-www-form-urlencoded")) {
11847
- return { 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
+ };
11848
12472
  }
11849
12473
  const body3 = text(own2(postData, "text"));
11850
12474
  if (own2(postData, "encoding") === "base64") {
@@ -11859,11 +12483,18 @@ function parseHar(value) {
11859
12483
  return entries.flatMap((entry, index) => {
11860
12484
  const request = own2(entry, "request");
11861
12485
  const [method, url] = isObject3(request) ? [text(own2(request, "method")).toUpperCase(), text(own2(request, "url"))] : ["", ""];
11862
- if (!isObject3(request) || !method || !url) 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.`);
11863
12488
  const warnings = [];
11864
12489
  const headers2 = list(own2(request, "headers")).map((header) => [text(own2(header, "name")), text(own2(header, "value"))]).filter(([name]) => name && !name.startsWith(":"));
11865
12490
  const path = URL.canParse(url) ? new URL(url).pathname : url;
11866
- return [{ 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
+ ];
11867
12498
  });
11868
12499
  }
11869
12500
 
@@ -11896,7 +12527,12 @@ function addressParts(raw) {
11896
12527
  const host = colon === -1 ? authority : authority.slice(0, colon);
11897
12528
  return {
11898
12529
  ...protocol !== void 0 && { protocol: back(protocol) },
11899
- ...user !== void 0 && { 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
+ },
11900
12536
  host: host.startsWith("[") ? [back(host)] : host.split(".").map(back),
11901
12537
  ...port !== void 0 && { port: back(port) },
11902
12538
  ...path !== void 0 && { path: path.split("/").map(back) },
@@ -11931,7 +12567,11 @@ var description = (value) => {
11931
12567
  const words = isObject4(value) ? text2(field3(value, "content")) : text2(value);
11932
12568
  return words ? { description: words } : {};
11933
12569
  };
11934
- var rows = (value) => objects(value).map((entry) => ({ 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
+ }));
11935
12575
  function headers(value) {
11936
12576
  if (typeof value !== "string") return rows(value);
11937
12577
  return value.split(/\r?\n/).filter((line2) => line2.trim()).map((line2) => {
@@ -11956,15 +12596,30 @@ function auth(value, item, warnings, top) {
11956
12596
  case "apikey":
11957
12597
  return { type, key: read("key"), value: read("value"), in: read("in") === "query" ? "query" : "header" };
11958
12598
  case "awsv4":
11959
- return { 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
+ };
11960
12607
  case "oauth2": {
11961
12608
  const grant = OAUTH_GRANTS_OF.get(read("grant_type"));
11962
12609
  if (!grant) {
11963
- warnings.push({ item, message: `Its auth is OAuth 2.0 with a grant the app does not have (${read("grant_type").slice(0, 40)}) and is left out: it is sent with no auth.` });
12610
+ warnings.push({
12611
+ item,
12612
+ message: `Its auth is OAuth 2.0 with a grant the app does not have (${read("grant_type").slice(0, 40)}) and is left out: it is sent with no auth.`
12613
+ });
11964
12614
  return { type: "none" };
11965
12615
  }
11966
- if (read("accessToken")) warnings.push({ item, message: "The OAuth 2.0 token in the file is not kept: get one in the Auth tab." });
11967
- if (read("addTokenTo") === "queryParams") warnings.push({ item, message: "Its OAuth 2.0 token was added to the address: the app sends it in the Authorization header." });
12616
+ if (read("accessToken"))
12617
+ warnings.push({ item, message: "The OAuth 2.0 token in the file is not kept: get one in the Auth tab." });
12618
+ if (read("addTokenTo") === "queryParams")
12619
+ warnings.push({
12620
+ item,
12621
+ message: "Its OAuth 2.0 token was added to the address: the app sends it in the Authorization header."
12622
+ });
11968
12623
  return {
11969
12624
  type,
11970
12625
  grant,
@@ -11980,7 +12635,10 @@ function auth(value, item, warnings, top) {
11980
12635
  };
11981
12636
  }
11982
12637
  default:
11983
- warnings.push({ 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
+ });
11984
12642
  return { type: "none" };
11985
12643
  }
11986
12644
  }
@@ -11998,21 +12656,30 @@ function scripts(value, item, warnings) {
11998
12656
  return Array.isArray(exec) ? exec.map(text2).join("\n") : text2(exec);
11999
12657
  }).join("\n");
12000
12658
  const others = objects(value).map((event) => text2(field3(event, "listen"))).filter((listen) => listen !== "prerequest" && listen !== "test");
12001
- if (others.length) 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
+ });
12002
12664
  return { pre: of2("prerequest"), post: of2("test") };
12003
12665
  }
12004
12666
  function address(value, item, warnings) {
12005
12667
  const inText = (url2) => (addressParts(url2).query ?? []).map((entry) => ({ key: entry.key, value: entry.value ?? "", enabled: true }));
12006
12668
  if (!isObject4(value)) return { url: text2(value), params: inText(text2(value)) };
12007
- const kept = (part, marks) => part.split(/(\{\{[^{}]*\}\})/).map((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("");
12008
12672
  const params = objects(field3(value, "query")).map((entry) => {
12009
12673
  const enabled = field3(entry, "disabled") !== true;
12010
- return { key: enabled ? kept(text2(field3(entry, "key")), /[#&=]/g) : text2(field3(entry, "key")), value: enabled ? kept(text2(field3(entry, "value")), /[#&]/g) : text2(field3(entry, "value")), enabled };
12674
+ return {
12675
+ key: enabled ? kept(text2(field3(entry, "key")), /[#&=]/g) : text2(field3(entry, "key")),
12676
+ value: enabled ? kept(text2(field3(entry, "value")), /[#&]/g) : text2(field3(entry, "value")),
12677
+ enabled
12678
+ };
12011
12679
  });
12012
- const off = params.filter((row) => !row.enabled).map((row) => row.key);
12013
- if (off.length) warnings.push({ item, message: `${off.length === 1 ? `A query parameter that is switched off (${off[0]}) is` : `${off.length} query parameters that are switched off are`} kept for an export, but the composer has no switch for a parameter: it does not show one, and saving the request there drops it.` });
12014
12680
  const host = field3(value, "host");
12015
- if (host === void 0) 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"))) };
12016
12683
  const variables2 = objects(field3(value, "variable"));
12017
12684
  const replaced = [];
12018
12685
  const named2 = [];
@@ -12029,11 +12696,21 @@ function address(value, item, warnings) {
12029
12696
  const protocol = field3(value, "protocol");
12030
12697
  const port = field3(value, "port");
12031
12698
  const path = field3(value, "path");
12032
- const query = objects(field3(value, "query")).filter((entry) => field3(entry, "disabled") !== true).map((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
+ );
12033
12702
  const hash = field3(value, "hash");
12034
12703
  const url = (protocol === void 0 ? "" : `${text2(protocol)}://`) + (isObject4(login) ? `${text2(field3(login, "user"))}${field3(login, "password") === void 0 ? "" : `:${text2(field3(login, "password"))}`}@` : "") + joined(host, ".") + (port === void 0 ? "" : `:${text2(port)}`) + (path === void 0 ? "" : `/${Array.isArray(path) ? joined(path, "/", inPath) : text2(path).replace(/^\//, "").split("/").map(inPath).join("/")}`) + (query.length ? `?${query.join("&")}` : "") + (hash === void 0 ? "" : `#${text2(hash)}`);
12035
- if (replaced.length) warnings.push({ item, message: `${replaced.length === 1 ? `The path variable ${replaced[0]} is` : `The path variables ${replaced.join(", ")} are`} replaced by ${replaced.length === 1 ? "its value" : "their values"} in the address: the app has no path variables.` });
12036
- if (named2.length) warnings.push({ item, message: `${named2.length === 1 ? `The path variable ${named2[0]} has` : `The path variables ${named2.join(", ")} have`} no value in the file: in the address ${named2.length === 1 ? "it is" : "they are"} ${named2.map((part) => `{{${part.slice(1)}}}`).join(", ")}, to be given a value as ${named2.length === 1 ? "a variable" : "variables"}.` });
12704
+ if (replaced.length)
12705
+ warnings.push({
12706
+ item,
12707
+ message: `${replaced.length === 1 ? `The path variable ${replaced[0]} is` : `The path variables ${replaced.join(", ")} are`} replaced by ${replaced.length === 1 ? "its value" : "their values"} in the address: the app has no path variables.`
12708
+ });
12709
+ if (named2.length)
12710
+ warnings.push({
12711
+ item,
12712
+ message: `${named2.length === 1 ? `The path variable ${named2[0]} has` : `The path variables ${named2.join(", ")} have`} no value in the file: in the address ${named2.length === 1 ? "it is" : "they are"} ${named2.map((part) => `{{${part.slice(1)}}}`).join(", ")}, to be given a value as ${named2.length === 1 ? "a variable" : "variables"}.`
12713
+ });
12037
12714
  return { url, params };
12038
12715
  }
12039
12716
  function body(value, item, warnings) {
@@ -12053,11 +12730,19 @@ function body(value, item, warnings) {
12053
12730
  return field3(part, "type") === "file" ? { key, enabled, kind: "file", fileId: "", fileName: file(field3(part, "src")) } : { key, enabled, kind: "text", value: text2(field3(part, "value")) };
12054
12731
  })
12055
12732
  },
12056
- ...isObject4(field3(value, "file")) && { 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
+ },
12057
12736
  // Postman keeps the variables as text, as the app does.
12058
- ...isObject4(field3(value, "graphql")) && { graphql: { query: text2(field3(field3(value, "graphql"), "query")), variables: text2(field3(field3(value, "graphql"), "variables")) } }
12737
+ ...isObject4(field3(value, "graphql")) && {
12738
+ graphql: {
12739
+ query: text2(field3(field3(value, "graphql"), "query")),
12740
+ variables: text2(field3(field3(value, "graphql"), "variables"))
12741
+ }
12742
+ }
12059
12743
  };
12060
- if (kept.multipart?.some((part) => part.kind === "file") || kept.binary) 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." });
12061
12746
  if (field3(value, "disabled") === true) return { mode: "none", ...kept };
12062
12747
  switch (field3(value, "mode")) {
12063
12748
  case "raw":
@@ -12075,7 +12760,10 @@ function body(value, item, warnings) {
12075
12760
  case "none":
12076
12761
  return { mode: "none", ...kept };
12077
12762
  default:
12078
- warnings.push({ 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
+ });
12079
12767
  return { mode: "none", ...kept };
12080
12768
  }
12081
12769
  }
@@ -12120,7 +12808,10 @@ function postmanCollection(value) {
12120
12808
  const method = text2(field3(source, "method") ?? "GET").toUpperCase();
12121
12809
  examples += list2(field3(entry, "response")).length;
12122
12810
  if (!url.trim() || !isHttpMethod(method)) {
12123
- warnings.push({ 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
+ });
12124
12815
  continue;
12125
12816
  }
12126
12817
  requests.push({
@@ -12145,7 +12836,11 @@ function postmanCollection(value) {
12145
12836
  };
12146
12837
  walk2(field3(value, "item"), void 0, 0);
12147
12838
  const item = `collection "${name}"`;
12148
- if (examples) 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
+ });
12149
12844
  return {
12150
12845
  payload: {
12151
12846
  $id: COLLECTION_FORMAT,
@@ -12177,9 +12872,18 @@ function postmanEnvironment(value) {
12177
12872
  meta: { id, createdAt: now, updatedAt: now },
12178
12873
  name,
12179
12874
  order: 0,
12180
- vars: objects(isObject4(value) ? field3(value, "values") : value).map((entry) => ({ 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
+ }))
12181
12880
  },
12182
- warnings: globals ? [{ item: `environment "${name}"`, message: "Postman's globals are imported as an environment: choose it as the active environment, or copy what you need into the app's globals." }] : []
12881
+ warnings: globals ? [
12882
+ {
12883
+ item: `environment "${name}"`,
12884
+ message: "Postman's globals are imported as an environment: choose it as the active environment, or copy what you need into the app's globals."
12885
+ }
12886
+ ] : []
12183
12887
  };
12184
12888
  }
12185
12889
 
@@ -12190,10 +12894,16 @@ function isInsomnia(value) {
12190
12894
  return field3(value, "_type") === "export" || Object.hasOwn(value, "__export_format") || /^\w+\.insomnia(\.rest)?\/\d/.test(text2(field3(value, "type")));
12191
12895
  }
12192
12896
  var VARIABLE = /\{\{\s*(?:_\.|_(?=\[))?((?:\w[\w-]*|\[(?:'[^'\]]*'|"[^"\]]*")\])(?:\.\w[\w-]*|\[(?:'[^'\]]*'|"[^"\]]*")\])*)\s*\}\}/g;
12193
- var variables = (value) => value.replace(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
+ );
12194
12901
  function foreign(value) {
12195
12902
  const found = [];
12196
- for (const [open, close] of [["{%", "%}"], ["{{", "}}"]]) {
12903
+ for (const [open, close] of [
12904
+ ["{%", "%}"],
12905
+ ["{{", "}}"]
12906
+ ]) {
12197
12907
  let at = value.indexOf(open);
12198
12908
  while (at !== -1) {
12199
12909
  const end = value.indexOf(close, at + 2);
@@ -12214,7 +12924,11 @@ function reader(item, warnings) {
12214
12924
  return turned;
12215
12925
  },
12216
12926
  done: () => {
12217
- if (templates.length) 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
+ });
12218
12932
  }
12219
12933
  };
12220
12934
  }
@@ -12230,7 +12944,11 @@ function environment(name, vars) {
12230
12944
  const now = Date.now();
12231
12945
  return { id, meta: { id, createdAt: now, updatedAt: now }, name, order: 0, vars };
12232
12946
  }
12233
- var pairs = (value, said2) => objects(value).map((entry) => ({ 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 !== "");
12234
12952
  function auth2(value, said2) {
12235
12953
  if (!isObject4(value) || typeof field3(value, "type") !== "string" || field3(value, "disabled") === true) return {};
12236
12954
  const type = text2(field3(value, "type"));
@@ -12243,18 +12961,44 @@ function auth2(value, said2) {
12243
12961
  case "bearer": {
12244
12962
  const prefix = read("prefix").trim();
12245
12963
  if (!prefix || prefix === "Bearer") return { auth: { type, bearer: { token: read("token") } } };
12246
- return { 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
+ };
12247
12974
  }
12248
12975
  case "apikey": {
12249
12976
  const place = text2(field3(value, "addTo"));
12250
12977
  if (place && place !== "header" && place !== "queryParams") return { auth: { type: `an API key in a ${place}` } };
12251
- return { 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
+ };
12252
12984
  }
12253
12985
  case "iam":
12254
- 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
+ };
12255
12998
  case "oauth2": {
12256
12999
  const grant = read("grantType");
12257
- 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" } } };
12258
13002
  return {
12259
13003
  auth: {
12260
13004
  type,
@@ -12291,8 +13035,10 @@ function body2(value, said2) {
12291
13035
  }
12292
13036
  };
12293
13037
  }
12294
- if (type === "application/x-www-form-urlencoded" && Array.isArray(params)) return { body: { mode: "urlencoded", urlencoded: pairs(params, said2) } };
12295
- if (typeof field3(value, "fileName") === "string" && typeof field3(value, "text") !== "string") return { body: { mode: "file", file: { src: field3(value, "fileName") } } };
13038
+ if (type === "application/x-www-form-urlencoded" && Array.isArray(params))
13039
+ return { body: { mode: "urlencoded", urlencoded: pairs(params, said2) } };
13040
+ if (typeof field3(value, "fileName") === "string" && typeof field3(value, "text") !== "string")
13041
+ return { body: { mode: "file", file: { src: field3(value, "fileName") } } };
12296
13042
  if (typeof field3(value, "text") !== "string" || !type && !field3(value, "text")) return {};
12297
13043
  const language = /json|graphql/.test(type) ? "json" : type.includes("xml") ? "xml" : type.includes("html") ? "html" : type.includes("javascript") ? "javascript" : "text";
12298
13044
  return { body: { mode: "raw", raw: said2(field3(value, "text")), options: { raw: { language } } } };
@@ -12301,7 +13047,11 @@ function builder(warnings) {
12301
13047
  const folderVariables = [];
12302
13048
  const scripts2 = (item, { pre, post }) => {
12303
13049
  const kinds = [text2(pre).trim() && "pre-request", text2(post).trim() && "after-response"].filter(Boolean);
12304
- if (kinds.length) 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
+ });
12305
13055
  };
12306
13056
  const request = ({ source, name, description: description2, scripts: own3 }) => {
12307
13057
  const item = `request "${name || "Request"}"`;
@@ -12313,7 +13063,11 @@ function builder(warnings) {
12313
13063
  method: text2(field3(source, "method")),
12314
13064
  description: description2,
12315
13065
  // Insomnia holds the query's rows beside the address, and sends both.
12316
- url: { ...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
+ },
12317
13071
  header: pairs(field3(source, "headers"), said2),
12318
13072
  ...body2(field3(source, "body"), said2),
12319
13073
  ...auth2(field3(source, "authentication"), said2)
@@ -12329,7 +13083,14 @@ function builder(warnings) {
12329
13083
  const variable = rowsOf(field3(source, "environment"), said2);
12330
13084
  folderVariables.push(...variable.map(({ key }) => ({ item, key })));
12331
13085
  const headers2 = pairs(field3(source, "headers"), said2).filter((header) => !header.disabled);
12332
- if (headers2.length) 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
+ });
12333
13094
  scripts2(item, own3);
12334
13095
  done();
12335
13096
  return { name, description: description2, item: children, variable, ...auth2(field3(source, "authentication"), said2) };
@@ -12337,7 +13098,11 @@ function builder(warnings) {
12337
13098
  const compare = (environments) => {
12338
13099
  for (const { item, key } of folderVariables) {
12339
13100
  const also = environments.find((entry) => entry.vars.some((row) => row.key === key));
12340
- if (also) 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
+ });
12341
13106
  }
12342
13107
  };
12343
13108
  return { request, folder, compare };
@@ -12349,7 +13114,10 @@ function format5(value) {
12349
13114
  const tree = field3(value, "collection");
12350
13115
  const held = field3(value, "environments");
12351
13116
  const type = text2(field3(value, "type"));
12352
- if (!Array.isArray(tree) && !isObject4(held)) 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
+ );
12353
13121
  const name = text2(field3(value, "name")).trim() || UNNAMED;
12354
13122
  const item = Array.isArray(tree) ? `collection "${name}"` : `environment "${name}"`;
12355
13123
  const { said: said2, done } = reader(item, warnings);
@@ -12358,15 +13126,26 @@ function format5(value) {
12358
13126
  if (depth > MAX_DEPTH) throw tooDeep();
12359
13127
  return objects(items).flatMap((source) => {
12360
13128
  const own3 = isObject4(field3(source, "scripts")) ? field3(source, "scripts") : {};
12361
- const entry = { 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
+ };
12362
13135
  if (Array.isArray(field3(source, "children"))) return [folder(entry, level(field3(source, "children"), depth + 1))];
12363
13136
  if (typeof field3(source, "method") === "string") return [request(entry)];
12364
- warnings.push({ 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
+ });
12365
13141
  return [];
12366
13142
  });
12367
13143
  };
12368
13144
  const base = isObject4(held) ? rowsOf(field3(held, "data"), said2) : [];
12369
- const subs = objects(isObject4(held) ? field3(held, "subEnvironments") : []).map((sub) => ({ 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
+ }));
12370
13149
  const spec = field3(value, "spec");
12371
13150
  const contents = isObject4(spec) ? field3(spec, "contents") : void 0;
12372
13151
  const left = [
@@ -12375,13 +13154,28 @@ function format5(value) {
12375
13154
  objects(field3(value, "testSuites")).length > 0 && "unit tests",
12376
13155
  isObject4(spec) && (typeof field3(spec, "file") === "string" || isObject4(contents) && Object.keys(contents).length > 0) && "API specification"
12377
13156
  ].filter((kind) => typeof kind === "string");
12378
- if (left.length) 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
+ });
12379
13162
  let result;
12380
13163
  if (Array.isArray(tree)) {
12381
- result = { collections: [{ info: { name, description: described(value) }, variable: base, item: level(tree, 1) }], environments: subs.map((sub) => environment(sub.name, sub.vars)), warnings };
13164
+ result = {
13165
+ collections: [{ info: { name, description: described(value) }, variable: base, item: level(tree, 1) }],
13166
+ environments: subs.map((sub) => environment(sub.name, sub.vars)),
13167
+ warnings
13168
+ };
12382
13169
  } else {
12383
- const merged = (own3) => [...own3, ...base.filter((row) => !own3.some((other) => other.key === row.key))];
12384
- result = { collections: [], environments: [environment(name, base), ...subs.map((sub) => environment(sub.name, merged(sub.vars)))], warnings };
13170
+ const merged = (own3) => [
13171
+ ...own3,
13172
+ ...base.filter((row) => !own3.some((other) => other.key === row.key))
13173
+ ];
13174
+ result = {
13175
+ collections: [],
13176
+ environments: [environment(name, base), ...subs.map((sub) => environment(sub.name, merged(sub.vars)))],
13177
+ warnings
13178
+ };
12385
13179
  }
12386
13180
  done();
12387
13181
  compare(result.environments);
@@ -12390,7 +13184,9 @@ function format5(value) {
12390
13184
  function format4(value) {
12391
13185
  const version = field3(value, "__export_format");
12392
13186
  if (version !== 4) {
12393
- throw new ImportError(`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
+ );
12394
13190
  }
12395
13191
  const warnings = [];
12396
13192
  const { request, folder, compare } = builder(warnings);
@@ -12411,7 +13207,12 @@ function format4(value) {
12411
13207
  return (parent === "" ? [] : children.get(parent) ?? []).flatMap((source) => {
12412
13208
  if (placed.has(source) || kind(source) !== "request" && kind(source) !== "request_group") return [];
12413
13209
  placed.add(source);
12414
- const entry = { 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
+ };
12415
13216
  return [kind(source) === "request_group" ? folder(entry, level(id(source), depth + 1)) : request(entry)];
12416
13217
  });
12417
13218
  };
@@ -12419,7 +13220,11 @@ function format4(value) {
12419
13220
  const collections = workspaces.map((workspace) => {
12420
13221
  const name = text2(field3(workspace, "name")).trim() || UNNAMED;
12421
13222
  const { said: said2, done } = reader(`collection "${name}"`, warnings);
12422
- const collection = { 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
+ };
12423
13228
  done();
12424
13229
  return collection;
12425
13230
  });
@@ -12432,16 +13237,38 @@ function format4(value) {
12432
13237
  done();
12433
13238
  return made;
12434
13239
  });
12435
- const known = new Set(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
+ );
12436
13243
  const loose = [...children.keys()].filter((parent) => parent !== "" && !known.has(parent)).flatMap((parent) => level(parent, 1));
12437
13244
  if (loose.length) collections.push({ info: { name: UNNAMED, description: void 0 }, variable: [], item: loose });
12438
- const unplaced = resources.filter((resource) => (kind(resource) === "request" || kind(resource) === "request_group") && !placed.has(resource)).length;
12439
- if (unplaced) warnings.push({ item: "file", message: `${unplaced} ${unplaced === 1 ? "request or folder is" : "requests and folders are"} left out: the file does not say which collection ${unplaced === 1 ? "it is" : "they are"} in.` });
13245
+ const unplaced = resources.filter(
13246
+ (resource) => (kind(resource) === "request" || kind(resource) === "request_group") && !placed.has(resource)
13247
+ ).length;
13248
+ if (unplaced)
13249
+ warnings.push({
13250
+ item: "file",
13251
+ message: `${unplaced} ${unplaced === 1 ? "request or folder is" : "requests and folders are"} left out: the file does not say which collection ${unplaced === 1 ? "it is" : "they are"} in.`
13252
+ });
12440
13253
  for (const resource of resources) {
12441
- if (kind(resource).endsWith("_request")) 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
+ });
12442
13259
  }
12443
- const others = [...new Set(resources.filter((resource) => !["workspace", "request", "request_group", "environment"].includes(kind(resource)) && !kind(resource).endsWith("_request") && !(kind(resource) === "cookie_jar" && objects(field3(resource, "cookies")).length === 0) && !(kind(resource) === "api_spec" && !text2(field3(resource, "contents")).trim())).map((resource) => kind(resource).slice(0, 30)))];
12444
- if (others.length) warnings.push({ item: "file", message: `What the file holds that is not a request, a folder or an environment is left out (${others.slice(0, 8).join(", ")}).` });
13260
+ const others = [
13261
+ ...new Set(
13262
+ resources.filter(
13263
+ (resource) => !["workspace", "request", "request_group", "environment"].includes(kind(resource)) && !kind(resource).endsWith("_request") && !(kind(resource) === "cookie_jar" && objects(field3(resource, "cookies")).length === 0) && !(kind(resource) === "api_spec" && !text2(field3(resource, "contents")).trim())
13264
+ ).map((resource) => kind(resource).slice(0, 30))
13265
+ )
13266
+ ];
13267
+ if (others.length)
13268
+ warnings.push({
13269
+ item: "file",
13270
+ message: `What the file holds that is not a request, a folder or an environment is left out (${others.slice(0, 8).join(", ")}).`
13271
+ });
12445
13272
  compare(environments);
12446
13273
  return { collections, environments, warnings };
12447
13274
  }
@@ -12487,13 +13314,19 @@ function openApiCollection(root) {
12487
13314
  const swagger = field3(root, "swagger") !== void 0;
12488
13315
  const version = text2(field3(root, swagger ? "swagger" : "openapi")).slice(0, 20);
12489
13316
  if (swagger ? !/^2(\.0)?$/.test(version) : !/^3(\.|$)/.test(version)) {
12490
- throw new ImportError(`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
+ );
12491
13320
  }
12492
13321
  const warnings = [];
12493
13322
  const info = field3(root, "info");
12494
13323
  const name = text2(isObject4(info) ? field3(info, "title") : "").trim() || UNNAMED2;
12495
13324
  const top = `collection "${name}"`;
12496
- if (!swagger && !/^3(\.[01](\.|$)|$)/.test(version)) 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
+ });
12497
13330
  let item = top;
12498
13331
  const unread = /* @__PURE__ */ new Map();
12499
13332
  const note = (kind, ref) => {
@@ -12542,7 +13375,8 @@ function openApiCollection(root) {
12542
13375
  const of2 = (raw, depth) => {
12543
13376
  const schema = resolve(raw);
12544
13377
  if (!isObject4(schema) || left-- <= 0 || depth > MAX_DEPTH || open.has(schema)) return null;
12545
- for (const key of ["example", "default", "const"]) 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);
12546
13380
  for (const key of ["examples", "enum"]) if (list3(field3(schema, key)).length) return list3(field3(schema, key))[0];
12547
13381
  open.add(schema);
12548
13382
  try {
@@ -12555,7 +13389,8 @@ function openApiCollection(root) {
12555
13389
  });
12556
13390
  parts.push(Object.fromEntries(sent.map((key) => [key, of2(field3(properties, key), depth + 1)])));
12557
13391
  }
12558
- if (parts.length) 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];
12559
13394
  const choice = [...list3(field3(schema, "oneOf")), ...list3(field3(schema, "anyOf"))][0];
12560
13395
  if (choice !== void 0) return of2(choice, depth + 1);
12561
13396
  const types = field3(schema, "type");
@@ -12581,20 +13416,43 @@ function openApiCollection(root) {
12581
13416
  const schema = resolve(raw);
12582
13417
  return isObject4(schema) && (field3(schema, "type") === "file" || field3(schema, "format") === "binary");
12583
13418
  };
12584
- const form = (type, fields) => type.startsWith("multipart/") ? { 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) })) };
12585
13425
  const bodyOf2 = (type, media) => {
12586
13426
  const kind = type.toLowerCase();
12587
13427
  const schema = resolve(field3(media, "schema"));
12588
13428
  const value = example(media) ?? sample(schema, true);
12589
13429
  if (kind.startsWith("multipart/") || kind === "application/x-www-form-urlencoded") {
12590
13430
  const properties = isObject4(schema) ? field3(schema, "properties") : void 0;
12591
- return { 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
+ };
12592
13442
  }
12593
- 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: [] };
12594
13445
  const language = /json|\*/.test(kind) ? "json" : kind.includes("xml") ? "xml" : kind.includes("html") ? "html" : kind.includes("javascript") ? "javascript" : "text";
12595
13446
  const raw = typeof value === "string" ? value : language === "json" && value !== null ? JSON.stringify(value, null, 2) : "";
12596
- if (!raw) warnings.push({ item, message: `Its body (${type.slice(0, 80)}) has no example in the file that the app can write out: the body is empty.` });
12597
- return { body: { mode: "raw", raw, options: { raw: { language } } }, header: kind.includes("*") || RAW_CONTENT_TYPES[language] === kind ? [] : [{ key: "Content-Type", value: type }] };
13447
+ if (!raw)
13448
+ warnings.push({
13449
+ item,
13450
+ message: `Its body (${type.slice(0, 80)}) has no example in the file that the app can write out: the body is empty.`
13451
+ });
13452
+ return {
13453
+ body: { mode: "raw", raw, options: { raw: { language } } },
13454
+ header: kind.includes("*") || RAW_CONTENT_TYPES[language] === kind ? [] : [{ key: "Content-Type", value: type }]
13455
+ };
12598
13456
  };
12599
13457
  const components = field3(root, "components");
12600
13458
  const schemes = swagger ? field3(root, "securityDefinitions") : isObject4(components) ? field3(components, "securitySchemes") : void 0;
@@ -12602,16 +13460,22 @@ function openApiCollection(root) {
12602
13460
  if (!Array.isArray(requirements)) return {};
12603
13461
  const names = Object.keys(requirements.find(isObject4) ?? {});
12604
13462
  if (!names.length) return { auth: { type: "noauth" } };
12605
- if (names.length > 1) 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
+ });
12606
13468
  const scheme = resolve(isObject4(schemes) ? field3(schemes, names[0]) : void 0);
12607
13469
  if (!isObject4(scheme)) return { auth: { type: `${names[0].slice(0, 60)}, which the file does not describe` } };
12608
13470
  const type = text2(field3(scheme, "type"));
12609
13471
  const how = text2(field3(scheme, "scheme")).toLowerCase();
12610
- if (type === "basic" || type === "http" && how === "basic") 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}}" } } };
12611
13474
  if (type === "http" && how === "bearer") return { auth: { type: "bearer", bearer: { token: "{{bearerToken}}" } } };
12612
13475
  if (type === "apiKey") {
12613
13476
  const place = text2(field3(scheme, "in"));
12614
- if (place !== "header" && place !== "query") 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"}` } };
12615
13479
  return { auth: { type: "apikey", apikey: { key: text2(field3(scheme, "name")), value: "{{apiKey}}", in: place } } };
12616
13480
  }
12617
13481
  if (type === "oauth2") {
@@ -12621,7 +13485,15 @@ function openApiCollection(root) {
12621
13485
  const client = flow("clientCredentials", "application");
12622
13486
  const password = flow("password", "password");
12623
13487
  const chosen = code ?? client ?? password;
12624
- if (!isObject4(chosen)) 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
+ };
12625
13497
  const asked = field3(requirements.find(isObject4) ?? {}, names[0]);
12626
13498
  const scopes = field3(chosen, "scopes");
12627
13499
  const scope = (Array.isArray(asked) && asked.length ? asked.map(text2) : isObject4(scopes) ? Object.keys(scopes) : []).join(" ");
@@ -12654,14 +13526,24 @@ function openApiCollection(root) {
12654
13526
  return withoutEndSlash(filled);
12655
13527
  };
12656
13528
  const host = text2(field3(root, "host"));
12657
- const base = swagger ? withoutEndSlash(`${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")) ?? "";
12658
13532
  if (!/^([a-z][a-z0-9+.-]*:\/\/|\{\{)/i.test(base)) {
12659
- warnings.push({ item: top, message: `The file gives no full address for its server (${base.slice(0, 80) || "it names none"}): give the collection's variable baseUrl the server's address before you send.` });
13533
+ warnings.push({
13534
+ item: top,
13535
+ message: `The file gives no full address for its server (${base.slice(0, 80) || "it names none"}): give the collection's variable baseUrl the server's address before you send.`
13536
+ });
12660
13537
  }
12661
13538
  const others = list3(field3(root, "servers")).length - 1;
12662
- if (!swagger && others > 0) 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
+ });
12663
13544
  const folders = /* @__PURE__ */ new Map();
12664
- for (const tag of list3(field3(root, "tags")).filter(isObject4)) folders.set(text2(field3(tag, "name")), { description: field3(tag, "description"), item: [] });
13545
+ for (const tag of list3(field3(root, "tags")).filter(isObject4))
13546
+ folders.set(text2(field3(tag, "name")), { description: field3(tag, "description"), item: [] });
12665
13547
  const loose = [];
12666
13548
  const paths = field3(root, "paths");
12667
13549
  for (const path of isObject4(paths) ? Object.keys(paths) : []) {
@@ -12674,7 +13556,8 @@ function openApiCollection(root) {
12674
13556
  const requestName = text2(field3(operation, "summary")).trim() || text2(field3(operation, "operationId")).trim() || `${method.toUpperCase()} ${path}`;
12675
13557
  item = `request "${requestName}"`;
12676
13558
  const parameters = /* @__PURE__ */ new Map();
12677
- for (const parameter of [...resolved(field3(pathItem, "parameters")), ...resolved(field3(operation, "parameters"))]) 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);
12678
13561
  const query = [];
12679
13562
  const header = [];
12680
13563
  const cookies = [];
@@ -12691,7 +13574,12 @@ function openApiCollection(root) {
12691
13574
  if (place === "formData") fields.push({ key, schema: parameter, value });
12692
13575
  else if (place === "query") query.push({ key, value: said(value) });
12693
13576
  else if (place === "cookie") cookies.push(`${key}=${said(value)}`);
12694
- else if (place === "header" && !/^(accept|content-type|authorization)$/i.test(key)) 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
+ });
12695
13583
  }
12696
13584
  if (cookies.length) header.push({ key: "Cookie", value: cookies.join("; ") });
12697
13585
  let body3;
@@ -12699,19 +13587,30 @@ function openApiCollection(root) {
12699
13587
  const requestBody = resolve(field3(operation, "requestBody"));
12700
13588
  const content = isObject4(requestBody) ? field3(requestBody, "content") : void 0;
12701
13589
  if (fields.length) {
12702
- body3 = form(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
+ );
12703
13594
  } else if (inBody || isObject4(content)) {
12704
13595
  const types = inBody ? consumes.length ? consumes : ["application/json"] : Object.keys(content).filter((type2) => isObject4(field3(content, type2)));
12705
13596
  const type = types.find((entry) => /json/i.test(entry)) ?? types[0];
12706
13597
  if (type !== void 0) {
12707
- if (!inBody && types.length > 1) 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
+ });
12708
13603
  const made2 = bodyOf2(type, inBody ?? field3(content, type));
12709
13604
  body3 = made2.body;
12710
13605
  header.push(...made2.header);
12711
13606
  }
12712
13607
  }
12713
13608
  const tags = list3(field3(operation, "tags")).map(text2).filter(Boolean);
12714
- if (tags.length > 1) 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
+ });
12715
13614
  const own3 = server(field3(operation, "servers")) ?? server(field3(pathItem, "servers"));
12716
13615
  const request = {
12717
13616
  name: requestName,
@@ -12719,7 +13618,11 @@ function openApiCollection(root) {
12719
13618
  method: method.toUpperCase(),
12720
13619
  description: field3(operation, "description"),
12721
13620
  // A `{name}` of the path is the variable `{{name}}`, which the composer asks for before it sends.
12722
- url: { 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
+ },
12723
13626
  header,
12724
13627
  ...body3 && { body: body3 },
12725
13628
  ...auth3(field3(operation, "security"))
@@ -12731,48 +13634,46 @@ function openApiCollection(root) {
12731
13634
  }
12732
13635
  }
12733
13636
  item = top;
12734
- const made = { 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
+ };
12735
13642
  const tagged = [...folders].filter(([, folder]) => folder.item.length).map(([folderName, folder]) => ({ name: folderName, description: folder.description, item: folder.item }));
12736
- if (!tagged.length && !loose.length) 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.");
12737
13645
  for (const [where, { outside, missing }] of unread) {
12738
- if (outside.size) warnings.push({ item: where, message: `It refers to ${outside.size === 1 ? "another file or an address" : `${outside.size} other files or addresses`} (${named([...outside])}). An import fetches nothing: what ${outside.size === 1 ? "it describes" : "they describe"} is left out.` });
12739
- if (missing.size) warnings.push({ item: where, message: `It refers to ${missing.size === 1 ? "a part" : `${missing.size} parts`} of the file that ${missing.size === 1 ? "is" : "are"} not there or ${missing.size === 1 ? "leads" : "lead"} back to ${missing.size === 1 ? "itself" : "themselves"} (${named([...missing])}), and ${missing.size === 1 ? "that is" : "those are"} left out.` });
13646
+ if (outside.size)
13647
+ warnings.push({
13648
+ item: where,
13649
+ message: `It refers to ${outside.size === 1 ? "another file or an address" : `${outside.size} other files or addresses`} (${named([...outside])}). An import fetches nothing: what ${outside.size === 1 ? "it describes" : "they describe"} is left out.`
13650
+ });
13651
+ if (missing.size)
13652
+ warnings.push({
13653
+ item: where,
13654
+ message: `It refers to ${missing.size === 1 ? "a part" : `${missing.size} parts`} of the file that ${missing.size === 1 ? "is" : "are"} not there or ${missing.size === 1 ? "leads" : "lead"} back to ${missing.size === 1 ? "itself" : "themselves"} (${named([...missing])}), and ${missing.size === 1 ? "that is" : "those are"} left out.`
13655
+ });
12740
13656
  }
12741
13657
  return { collection: { ...made, item: [...tagged, ...loose] }, warnings };
12742
13658
  }
12743
13659
 
12744
- // ../core/src/import/script-scan.ts
12745
- var START = String.raw`(?<![\w$.])`;
12746
- var escape = (text3) => text3.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
12747
- var member = (name) => ({ api: name, finds: [[name, new RegExp(`${START}${escape(name)}(?![\\w$])`)]] });
12748
- var required = (module) => [`require('${module}')`, new RegExp(`${START}require\\(\\s*["']${escape(module)}["']\\s*\\)`)];
12749
- var called = (name) => [name, new RegExp(`${START}${escape(name)}\\s*\\(`)];
12750
- var used = (name) => [name, new RegExp(`${START}${escape(name)}\\s*[.(]`)];
12751
- var SCRIPT_SCAN = [
12752
- ...["pm.cookies.get", "pm.cookies.has", "pm.cookies.toObject", "pm.cookies.jar", "pm.visualizer.set", "pm.vault.get", "pm.require", "postman.getResponseCookie"].map(member),
12753
- { api: "legacy library globals (_, CryptoJS, tv4, cheerio, xml2Json, Backbone)", finds: ["tv4", "cheerio", "xml2Json", "Backbone"].map(used) },
12754
- { api: "clearTimeout, setInterval, clearInterval", finds: ["clearTimeout", "setInterval", "clearInterval"].map(called) },
12755
- ...["ajv", "cheerio", "csv-parse/lib/sync", "postman-collection", "tv4", "xml2js"].map((module) => ({ api: `require('${module}')`, finds: [required(module)] })),
12756
- {
12757
- api: "require of a Node module (buffer, events, path, querystring, stream, string_decoder, timers, url, util)",
12758
- finds: ["buffer", "events", "path", "querystring", "stream", "string_decoder", "timers", "url", "util"].map(required)
12759
- }
12760
- ];
12761
- function unsupportedApis(script) {
12762
- return SCRIPT_SCAN.flatMap((entry) => entry.finds).filter(([, pattern]) => pattern.test(script)).map(([label]) => label);
12763
- }
12764
-
12765
13660
  // ../core/src/import/yaml.ts
12766
13661
  var import_yaml = __toESM(require_dist(), 1);
12767
13662
  var MAX_YAML_ALIASES = 100;
12768
13663
  var MAX_YAML_CHARS = 1024 * 1024;
12769
13664
  var YAML_ALIASES = `The YAML file repeats parts of itself through aliases ${MAX_YAML_ALIASES} times or more and is refused: a file written that way grows without end as it is read.`;
12770
- var YAML_TOO_LARGE = "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.";
12771
13666
  var NOT_READABLE = "The file is neither JSON nor YAML that can be read, so it is not a file Wayfarer can import.";
12772
13667
  function parseYaml(text3) {
12773
13668
  if (text3.length > MAX_YAML_CHARS) throw new ImportError(YAML_TOO_LARGE);
12774
13669
  try {
12775
- return (0, import_yaml.parse)(text3, { 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
+ });
12776
13677
  } catch (error) {
12777
13678
  throw new ImportError(error instanceof Error && /alias count/i.test(error.message) ? YAML_ALIASES : NOT_READABLE);
12778
13679
  }
@@ -12812,7 +13713,12 @@ function scriptWarnings(item, scripts2) {
12812
13713
  const found = unsupportedApis(scripts2[kind]);
12813
13714
  if (!found.length) return [];
12814
13715
  const names = found.length > 1 ? `${found.slice(0, -1).join(", ")} and ${found.at(-1)}` : found[0];
12815
- return [{ 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
+ ];
12816
13722
  });
12817
13723
  }
12818
13724
  function inheritedScriptWarnings(payload) {
@@ -12827,9 +13733,13 @@ function curlToRequest(text3) {
12827
13733
  const parsed2 = parseCurl(text3);
12828
13734
  const { content, warnings } = curlRequest(parsed2);
12829
13735
  const issues = validateRequestContent(content, "request");
12830
- if (issues.length) throw new ImportError("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);
12831
13737
  const address2 = URL.parse(parsed2.request.url);
12832
- 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
+ };
12833
13743
  }
12834
13744
  function built(format, name, requests) {
12835
13745
  const now = Date.now();
@@ -12838,7 +13748,15 @@ function built(format, name, requests) {
12838
13748
  const payload = {
12839
13749
  $id: COLLECTION_FORMAT,
12840
13750
  meta: top,
12841
- collection: { 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
+ },
12842
13760
  folders: [],
12843
13761
  requests: requests.map(({ name: requestName, content }, order) => {
12844
13762
  const own3 = meta();
@@ -12850,7 +13768,9 @@ function built(format, name, requests) {
12850
13768
  format,
12851
13769
  formatName: IMPORT_FORMATS[format],
12852
13770
  counts: { collections: 1, folders: 0, requests: requests.length, environments: 0 },
12853
- warnings: requests.flatMap(({ 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
+ )
12854
13774
  },
12855
13775
  collections: [added(payload)],
12856
13776
  environments: []
@@ -12858,10 +13778,57 @@ function built(format, name, requests) {
12858
13778
  }
12859
13779
  function added(payload) {
12860
13780
  const { payload: checked, plan, errors } = importCollection(payload, { duplicateAsNew: true });
12861
- if (!checked || !plan) throw new ImportError(`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 ?? []);
12862
13782
  return { payload: checked, plan };
12863
13783
  }
12864
13784
  function importText(text3, options = {}) {
13785
+ const imported = readText(text3, options);
13786
+ return {
13787
+ ...imported,
13788
+ report: {
13789
+ ...imported.report,
13790
+ warnings: [...imported.report.warnings, ...unsetVariableWarnings(imported.collections, imported.environments)]
13791
+ }
13792
+ };
13793
+ }
13794
+ var PLAIN_VARIABLE = /^[\w.-]+$/;
13795
+ var PLACEHOLDERS = /{{([^{}]*)}}/g;
13796
+ var SCRIPT_SETS = /\.set\(\s*["'`]([^"'`]+)["'`]/g;
13797
+ function unsetVariableWarnings(collections, environments) {
13798
+ return collections.flatMap(({ payload }) => {
13799
+ const set = /* @__PURE__ */ new Set([
13800
+ ...[payload.collection, ...payload.folders].flatMap((holder) => holder.variables.map((row) => row.key)),
13801
+ ...environments.flatMap((environment2) => environment2.vars.map((row) => row.key))
13802
+ ]);
13803
+ const scripts2 = [payload.collection, ...payload.folders, ...payload.requests].flatMap(({ scripts: scripts3 }) => [
13804
+ scripts3.pre,
13805
+ scripts3.post
13806
+ ]);
13807
+ for (const script of scripts2) for (const [, name] of script.matchAll(SCRIPT_SETS)) set.add(name);
13808
+ const uses = payload.requests.map(({ url, headers: headers2, body: body3, auth: auth3 }) => {
13809
+ const text3 = JSON.stringify({ url, headers: headers2, body: body3, auth: auth3 });
13810
+ return new Set(
13811
+ [...text3.matchAll(PLACEHOLDERS)].map(([, name]) => name.trim()).filter((name) => PLAIN_VARIABLE.test(name))
13812
+ );
13813
+ });
13814
+ const unset = [...new Set(uses.flatMap((names2) => [...names2]))].filter((name) => !set.has(name)).sort();
13815
+ if (!unset.length) return [];
13816
+ const using = uses.filter((names2) => unset.some((name) => names2.has(name))).length;
13817
+ const total = payload.requests.length;
13818
+ const listed = unset.slice(0, unset.length > 5 ? 5 : unset.length).map((name) => `{{${name}}}`);
13819
+ if (unset.length > 5) listed.push(`${unset.length - 5} more`);
13820
+ const names = listed.length < 2 ? listed[0] : `${listed.slice(0, -1).join(", ")} and ${listed.at(-1)}`;
13821
+ const which = using === total ? total === 1 ? "its request uses" : total === 2 ? "both requests use" : `all ${total} requests use` : `${using} of its ${total} requests ${using === 1 ? "uses" : "use"}`;
13822
+ const it = unset.length === 1 ? "it" : "them";
13823
+ return [
13824
+ {
13825
+ item: `collection "${payload.collection.name}"`,
13826
+ message: `${names} ${unset.length === 1 ? "has" : "have"} no value in the file, and ${which} ${it}: give ${it} a value in an environment or in the collection's settings before you send.`
13827
+ }
13828
+ ];
13829
+ });
13830
+ }
13831
+ function readText(text3, options) {
12865
13832
  if (isOversizedImport(text3)) throw new ImportError(IMPORT_TOO_LARGE);
12866
13833
  if (text3.startsWith("\uFEFF")) text3 = text3.slice(1);
12867
13834
  if (isCurlCommand(text3)) {
@@ -12873,8 +13840,12 @@ function importText(text3, options = {}) {
12873
13840
  if (!json.ok && (typeof value !== "object" || value === null)) throw new ImportError(NOT_READABLE);
12874
13841
  const format = detectFormat(value);
12875
13842
  if (!format) {
12876
- if (of(idOf(value), "wayfarer/workspace")) throw new ImportError("This is a workspace backup, which replaces everything. Restore it in Settings.");
12877
- if (isPostmanV1(value)) throw new ImportError("This is a Postman Collection v1 file, which Wayfarer does not read. In Postman, export the collection as Collection v2.1 and import that.");
13843
+ if (of(idOf(value), "wayfarer/workspace"))
13844
+ throw new ImportError("This is a workspace backup, which replaces everything. Restore it in Settings.");
13845
+ if (isPostmanV1(value))
13846
+ throw new ImportError(
13847
+ "This is a Postman Collection v1 file, which Wayfarer does not read. In Postman, export the collection as Collection v2.1 and import that."
13848
+ );
12878
13849
  throw new ImportError(UNKNOWN);
12879
13850
  }
12880
13851
  const report = (counts2, warnings) => ({
@@ -12884,7 +13855,10 @@ function importText(text3, options = {}) {
12884
13855
  warnings
12885
13856
  });
12886
13857
  if (format === "har") {
12887
- const requests = parseHar(value).map(({ name, request, warnings }) => ({ name, ...curlRequest({ request, warnings }) }));
13858
+ const requests = parseHar(value).map(({ name, request, warnings }) => ({
13859
+ name,
13860
+ ...curlRequest({ request, warnings })
13861
+ }));
12888
13862
  return built("har", "Imported from HAR", requests);
12889
13863
  }
12890
13864
  if (format === "postman-collection" || format === "openapi") {
@@ -12892,7 +13866,11 @@ function importText(text3, options = {}) {
12892
13866
  const { payload: payload2, warnings } = postmanCollection(source.collection);
12893
13867
  const collection = added(payload2);
12894
13868
  return {
12895
- report: report({ collections: 1, folders: payload2.folders.length, requests: payload2.requests.length }, [...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
+ ]),
12896
13874
  collections: [collection],
12897
13875
  environments: []
12898
13876
  };
@@ -12905,10 +13883,15 @@ function importText(text3, options = {}) {
12905
13883
  if (issues.length) throw new ImportError("The file's environments are not ones the app can hold.", issues);
12906
13884
  const count = (of2) => collections.reduce((sum, { payload: payload2 }) => sum + of2(payload2).length, 0);
12907
13885
  return {
12908
- report: report({ collections: collections.length, folders: count((payload2) => payload2.folders), requests: count((payload2) => payload2.requests), environments: environments.length }, [
12909
- ...warnings,
12910
- ...read.flatMap((collection) => collection.warnings)
12911
- ]),
13886
+ report: report(
13887
+ {
13888
+ collections: collections.length,
13889
+ folders: count((payload2) => payload2.folders),
13890
+ requests: count((payload2) => payload2.requests),
13891
+ environments: environments.length
13892
+ },
13893
+ [...warnings, ...read.flatMap((collection) => collection.warnings)]
13894
+ ),
12912
13895
  collections,
12913
13896
  environments
12914
13897
  };
@@ -12921,12 +13904,28 @@ function importText(text3, options = {}) {
12921
13904
  }
12922
13905
  if (format === "wayfarer-environments") {
12923
13906
  const result = validateEnvironmentExport(value);
12924
- if (!result.payload) throw new ImportError("The file is not a valid Wayfarer environments file.", (result.errors ?? []).map((message) => ({ path: "", message })));
12925
- return { report: report({ environments: result.payload.length }, []), collections: [], environments: result.payload };
13907
+ if (!result.payload)
13908
+ throw new ImportError(
13909
+ "The file is not a valid Wayfarer environments file.",
13910
+ (result.errors ?? []).map((message) => ({ path: "", message }))
13911
+ );
13912
+ return {
13913
+ report: report({ environments: result.payload.length }, []),
13914
+ collections: [],
13915
+ environments: result.payload
13916
+ };
12926
13917
  }
12927
13918
  const { payload, plan, errors } = importCollection(value, options);
12928
- if (!payload || !plan) throw new ImportError("The file is not a valid Wayfarer collection.", errors ?? []);
12929
- return { report: report({ collections: 1, folders: payload.folders.length, requests: payload.requests.length }, inheritedScriptWarnings(payload)), collections: [{ payload, plan }], environments: [] };
13919
+ if (!payload || !plan)
13920
+ throw new ImportError("This file is not a Wayfarer collection. What does not match is listed below.", errors ?? []);
13921
+ return {
13922
+ report: report(
13923
+ { collections: 1, folders: payload.folders.length, requests: payload.requests.length },
13924
+ inheritedScriptWarnings(payload)
13925
+ ),
13926
+ collections: [{ payload, plan }],
13927
+ environments: []
13928
+ };
12930
13929
  }
12931
13930
 
12932
13931
  // ../core/src/run/report.ts
@@ -12939,10 +13938,21 @@ var counts = (results) => ({
12939
13938
  errors: results.filter((result) => result.error !== void 0).length
12940
13939
  });
12941
13940
  function runReportJson(report, info) {
12942
- return { $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
+ };
12943
13950
  }
12944
13951
  function xml(text3) {
12945
- return text3.replace(/[^\t\n\r -퟿-�\u{10000}-\u{10FFFF}]/gu, "\uFFFD").replace(/[&<>"\t\n\r]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", " ": "&#9;", "\n": "&#10;", "\r": "&#13;" })[char] ?? char);
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
+ );
12946
13956
  }
12947
13957
  var seconds = (ms) => ((ms ?? 0) / 1e3).toFixed(3);
12948
13958
  function junitXml(report, info) {
@@ -12953,12 +13963,18 @@ function junitXml(report, info) {
12953
13963
  <failure type="AssertionFailure" message="${xml(test.error ?? "")}"/>
12954
13964
  </testcase>`
12955
13965
  );
12956
- if (result.error !== void 0) cases.push(` <testcase name="${classname}" classname="${classname}">
13966
+ if (result.error !== void 0)
13967
+ cases.push(
13968
+ ` <testcase name="${classname}" classname="${classname}">
12957
13969
  <error type="Error" message="${xml(result.error)}"/>
12958
- </testcase>`);
12959
- 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}">
12960
13975
  <skipped message="pm.execution.skipRequest()"/>
12961
- </testcase>`);
13976
+ </testcase>`
13977
+ );
12962
13978
  const totals2 = counts([result]);
12963
13979
  const name = xml(report.iterations > 1 ? `${result.name} (iteration ${result.iteration + 1})` : result.name);
12964
13980
  const open = ` <testsuite name="${name}" id="${xml(result.id)}" tests="${cases.length}" failures="${totals2.failedTests}" errors="${totals2.errors}" skipped="${totals2.skipped}" time="${seconds(result.durationMs)}"`;
@@ -12976,6 +13992,97 @@ ${cases.join("\n")}
12976
13992
  ""
12977
13993
  ].join("\n");
12978
13994
  }
13995
+ var REPORT_COLOURS = {
13996
+ light: {
13997
+ canvas: "#FBFAF8",
13998
+ panel: "#F6F5F2",
13999
+ primary: "#26231E",
14000
+ secondary: "#55524D",
14001
+ tertiary: "#726F69",
14002
+ line: "#DEDAD3",
14003
+ error: "#A51F1E"
14004
+ },
14005
+ dark: {
14006
+ canvas: "#161513",
14007
+ panel: "#22211E",
14008
+ primary: "#EBE9E4",
14009
+ secondary: "#C0BDB6",
14010
+ tertiary: "#9A9790",
14011
+ line: "#423F39",
14012
+ error: "#F7958A"
14013
+ }
14014
+ };
14015
+ function html(text3) {
14016
+ return text3.replace(
14017
+ /[&<>"']/g,
14018
+ (char) => ({ "&": "&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
+ }
12979
14086
 
12980
14087
  // src/run.ts
12981
14088
  import variant from "@jitl/quickjs-wasmfile-release-sync";
@@ -13020,10 +14127,16 @@ async function runCollection(input, options) {
13020
14127
  const transport = new FetchTransport();
13021
14128
  const ports = {
13022
14129
  runScript: async (script, env, response, { send: send2, ...extras }) => {
13023
- const result = await runScript(await engine(), script, { ...extras, environment: env, ...response && { response: scriptResponse(response) } }, SCRIPT_LIMITS, {
13024
- libraries: await loadLibraries(script, loadLibrary, libraries),
13025
- ...send2 && { send: send2 }
13026
- });
14130
+ const result = await runScript(
14131
+ await engine(),
14132
+ script,
14133
+ { ...extras, environment: env, ...response && { response: scriptResponse(response) } },
14134
+ SCRIPT_LIMITS,
14135
+ {
14136
+ libraries: await loadLibraries(script, loadLibrary, libraries),
14137
+ ...send2 && { send: send2 }
14138
+ }
14139
+ );
13027
14140
  if (result.limit) engine = engines();
13028
14141
  return result;
13029
14142
  },
@@ -13038,7 +14151,9 @@ async function runCollection(input, options) {
13038
14151
  environment: () => environment2 && {
13039
14152
  name: environment2.name,
13040
14153
  vars: environment2.vars,
13041
- change: (changes) => Promise.resolve(environment2 && (environment2 = { ...environment2, vars: applyVariableChanges(environment2.vars, changes) }))
14154
+ change: (changes) => Promise.resolve(
14155
+ environment2 && (environment2 = { ...environment2, vars: applyVariableChanges(environment2.vars, changes) })
14156
+ )
13042
14157
  },
13043
14158
  globals: () => globals,
13044
14159
  changeGlobals: (changes) => Promise.resolve(globals = applyVariableChanges(globals, changes)),
@@ -13060,7 +14175,13 @@ async function runCollection(input, options) {
13060
14175
  const send = async (doc, iteration, signal) => {
13061
14176
  const ancestors = ancestorsOf(source.collection, source.folders, doc.folderId);
13062
14177
  const folder = folderChain(source.folders, doc.folderId).flatMap((entry) => entry.variables);
13063
- const scopes = () => ({ 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
+ });
13064
14185
  const { outcome } = await runOutcome(
13065
14186
  () => exchange(
13066
14187
  {
@@ -13073,7 +14194,10 @@ async function runCollection(input, options) {
13073
14194
  scopes,
13074
14195
  ancestors,
13075
14196
  readSecrets: async (ids) => {
13076
- if (!vault) 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
+ );
13077
14201
  const found = /* @__PURE__ */ new Map();
13078
14202
  for (const id of ids) {
13079
14203
  const value = await readSecret(id);
@@ -13081,12 +14205,27 @@ async function runCollection(input, options) {
13081
14205
  }
13082
14206
  return found;
13083
14207
  },
13084
- readFile: (ref) => Promise.reject(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
+ ),
13085
14213
  blockUnresolved: true,
13086
- 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
+ }
13087
14219
  }),
13088
- info: { requestName: doc.name, requestId: doc.id },
13089
- 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
+ },
13090
14229
  inherited: ancestors,
13091
14230
  folderVariables: () => folder,
13092
14231
  signal,
@@ -13127,9 +14266,10 @@ Options:
13127
14266
  -n, --iterations <n> How many iterations (default: one per row of the
13128
14267
  data file, or 1).
13129
14268
  --collection <name> Which collection of a file that holds several.
13130
- --reporter <list> cli, json, junit, comma-separated (default cli).
13131
- json writes wayfarer-run.json and junit writes
13132
- 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.
13133
14273
  --out <dir> Where the reports are written (default: here).
13134
14274
  --bail Stop at the first failure.
13135
14275
  --timeout <ms> Give up on a request after this long (default: no
@@ -13148,46 +14288,64 @@ Exit codes: 0 every request answered and every test passed; 1 a test failed
13148
14288
  or a request got no response; 2 nothing was run (see the message).`;
13149
14289
  var Refusal = class extends Error {
13150
14290
  };
13151
- async function readText(file, what) {
14291
+ async function readText2(file, what) {
13152
14292
  try {
13153
14293
  return await readFile(file, "utf8");
13154
14294
  } catch (error) {
13155
- 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
+ );
13156
14298
  }
13157
14299
  }
13158
14300
  function pick(list4, name, what, file) {
13159
14301
  if (name !== void 0) {
13160
14302
  const found = list4.find((entry) => entry.name === name);
13161
- if (!found) 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
+ );
13162
14307
  return found;
13163
14308
  }
13164
- 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
+ );
13165
14313
  const [only] = list4;
13166
14314
  if (!only) throw new Refusal(`${file} holds no ${what}.`);
13167
14315
  return only;
13168
14316
  }
13169
14317
  async function openVault(record, secrets, file) {
13170
14318
  const passphrase = process.env["WAYFARER_VAULT_PASSPHRASE"];
13171
- if (passphrase === void 0) 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.`);
13172
14321
  const dek = await unwrapDek(record, passphrase);
13173
14322
  if (!dek) throw new Refusal(`WAYFARER_VAULT_PASSPHRASE is not the passphrase of the vault in ${file}.`);
13174
14323
  return { dek, secrets: new Map(secrets.map((secret) => [secret.id, secret.envelope])) };
13175
14324
  }
13176
14325
  async function readSource(file, collection) {
13177
- const text3 = await readText(file, "file");
14326
+ const text3 = await readText2(file, "file");
13178
14327
  const parsed2 = parseJson(text3);
13179
14328
  const id = parsed2.ok && typeof parsed2.value === "object" && parsed2.value !== null && "$id" in parsed2.value ? String(parsed2.value.$id) : "";
13180
14329
  if (id !== "wayfarer/workspace" && !id.startsWith("wayfarer/workspace/")) {
13181
14330
  try {
13182
14331
  const { collections } = importText(text3);
13183
- return { 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
+ };
13184
14341
  } catch (error) {
13185
14342
  if (error instanceof Error && error.name === "ImportError") throw new Refusal(`${file}: ${error.message}`);
13186
14343
  throw error;
13187
14344
  }
13188
14345
  }
13189
14346
  const errors = validateWorkspaceFile(parsed2.ok ? parsed2.value : void 0);
13190
- if (errors.length) 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:
13191
14349
  ${errors.join("\n ")}`);
13192
14350
  const { stores } = parsed2.ok ? parsed2.value : {};
13193
14351
  const chosen = pick(stores.collections, collection, "collection", file);
@@ -13207,12 +14365,14 @@ async function readSource(file, collection) {
13207
14365
  ...environment2 && { environment: { name: environment2.name, vars: environment2.vars } },
13208
14366
  globals: meta(META_GLOBALS_KEY)?.["variables"] ?? [],
13209
14367
  // A backup's secrets are useful only with the passphrase: without one, a request that needs a secret says so.
13210
- ...vault && process.env["WAYFARER_VAULT_PASSPHRASE"] !== void 0 && { 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
+ }
13211
14371
  };
13212
14372
  }
13213
14373
  async function readEnvironment(file) {
13214
14374
  try {
13215
- const { environments } = importText(await readText(file, "environment"));
14375
+ const { environments } = importText(await readText2(file, "environment"));
13216
14376
  const { name, vars } = pick(environments, void 0, "environment", file);
13217
14377
  return { name, vars };
13218
14378
  } catch (error) {
@@ -13221,9 +14381,12 @@ async function readEnvironment(file) {
13221
14381
  }
13222
14382
  }
13223
14383
  async function readVaultFile(file) {
13224
- const parsed2 = parseJson(await readText(file, "vault file"));
14384
+ const parsed2 = parseJson(await readText2(file, "vault file"));
13225
14385
  const issues = parsed2.ok ? validateVaultFile(parsed2.value) : [{ path: "", message: "not JSON" }];
13226
- if (issues.length) 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
+ );
13227
14390
  const { vault, secrets } = parsed2.ok ? parsed2.value : {};
13228
14391
  return openVault(vault, secrets, file);
13229
14392
  }
@@ -13234,21 +14397,22 @@ function scriptHolders(source) {
13234
14397
  ...source.requests.filter((request) => scriptsOf(request).length).map((request) => `the request "${request.name}"`)
13235
14398
  ];
13236
14399
  }
13237
- function line(result, iterations) {
14400
+ function line(result, iterations, said2) {
13238
14401
  const mark = runFailed(result) ? "\u2717" : result.skipped ? "-" : "\u2713";
13239
14402
  const where = iterations > 1 ? ` (iteration ${result.iteration + 1})` : "";
13240
14403
  const status = result.skipped ? "skipped" : result.status ? `${result.status} ${result.statusText ?? ""}`.trim() : "no response";
13241
14404
  const tests2 = result.tests.map((test) => `
13242
14405
  ${test.passed ? "\u2713" : "\u2717"} ${test.name}${test.error ? `: ${test.error}` : ""}`).join("");
13243
- const error = result.error !== void 0 ? `
13244
- ${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}`;
13245
14410
  return `${mark} ${result.name}${where}: ${status}${result.durationMs !== void 0 ? `, ${result.durationMs} ms` : ""}${tests2}${error}`;
13246
14411
  }
13247
14412
  function summary(report) {
13248
- const tests2 = report.results.flatMap((result) => result.tests);
13249
- const failed = report.results.filter(runFailed).length;
13250
- const ended = { completed: "", cancelled: " The run was cancelled.", failed: " The run stopped at the first failure.", ended: ` The run ended: ${report.reason ?? ""}` }[report.ended];
13251
- return `${report.results.length} results in ${report.iterations} iteration${report.iterations === 1 ? "" : "s"}; ${failed} failed; ${tests2.filter((test) => test.passed).length} of ${tests2.length} tests passed.${ended}`;
14413
+ const shared = sharedErrors(report.results).map(({ error, count }) => `
14414
+ ${sharedErrorHeading(count)} ${error}`);
14415
+ return `${runEnding(report)} ${runCounts(report)}${shared.join("")}`;
13252
14416
  }
13253
14417
  async function main(argv) {
13254
14418
  const { values, positionals } = parseArgs({
@@ -13271,7 +14435,7 @@ async function main(argv) {
13271
14435
  }
13272
14436
  });
13273
14437
  if (values.version) {
13274
- console.log("2.0.2");
14438
+ console.log("2.1.0");
13275
14439
  return 0;
13276
14440
  }
13277
14441
  const [command, file, ...rest] = positionals;
@@ -13279,11 +14443,14 @@ async function main(argv) {
13279
14443
  console.log(USAGE);
13280
14444
  return values.help ? 0 : 2;
13281
14445
  }
13282
- if (command !== "run" || file === void 0 || rest.length) 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(" ")}
13283
14448
 
13284
14449
  ${USAGE}`);
13285
14450
  const reporters = new Set(values.reporter.split(",").map((name) => name.trim()));
13286
- for (const name of reporters) if (!["cli", "json", "junit"].includes(name)) throw new Refusal(`Unknown reporter "${name}": choose from cli, json and junit.`);
14451
+ for (const name of reporters)
14452
+ if (!["cli", "json", "junit", "html"].includes(name))
14453
+ throw new Refusal(`Unknown reporter "${name}": choose from cli, json, junit and html.`);
13287
14454
  const number = (text3, flag2) => {
13288
14455
  if (text3 === void 0) return void 0;
13289
14456
  if (!/^\d+$/.test(text3)) throw new Refusal(`--${flag2} takes a whole number, not "${text3}".`);
@@ -13295,43 +14462,56 @@ ${USAGE}`);
13295
14462
  const read = await readSource(file, values.collection);
13296
14463
  const holders = scriptHolders(read.source);
13297
14464
  if (holders.length && !values["allow-scripts"]) {
13298
- throw new Refusal(`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
+ );
13299
14468
  }
13300
14469
  const environment2 = values.environment === void 0 ? read.environment : await readEnvironment(values.environment);
13301
14470
  const vault = values["vault-file"] === void 0 ? read.vault : await readVaultFile(values["vault-file"]);
13302
14471
  let data;
13303
14472
  if (values.data !== void 0) {
13304
14473
  try {
13305
- data = parseDataFile(await readText(values.data, "data file"), basename(values.data));
14474
+ data = parseDataFile(await readText2(values.data, "data file"), basename(values.data));
13306
14475
  } catch (error) {
13307
- 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}`);
13308
14478
  throw error;
13309
14479
  }
13310
14480
  }
13311
14481
  if (values.insecure) process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";
13312
14482
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
13313
- if (reporters.has("json") || reporters.has("junit")) {
14483
+ if (reporters.has("json") || reporters.has("junit") || reporters.has("html")) {
13314
14484
  await mkdir(values.out, { recursive: true }).catch((error) => {
13315
14485
  throw new Refusal(`--out ${values.out}: ${error instanceof Error ? error.message : String(error)}`);
13316
14486
  });
13317
14487
  }
14488
+ const said2 = /* @__PURE__ */ new Set();
13318
14489
  const report = await runCollection(
13319
- { 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
+ },
13320
14498
  {
13321
14499
  ...iterations !== void 0 && { iterations },
13322
14500
  ...data && { data },
13323
14501
  stopOnFailure: values.bail,
13324
14502
  onResult: (result) => {
13325
- if (reporters.has("cli")) console.log(line(result, iterations ?? data?.length ?? 1));
14503
+ if (reporters.has("cli")) console.log(line(result, iterations ?? data?.length ?? 1, said2));
13326
14504
  }
13327
14505
  }
13328
14506
  );
13329
14507
  const info = { name: read.source.collection.name, startedAt };
13330
14508
  if (reporters.has("cli")) console.log(`
13331
14509
  ${summary(report)}`);
13332
- if (reporters.has("json")) 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)}
13333
14512
  `);
13334
14513
  if (reporters.has("junit")) await writeFile(join(values.out, "wayfarer-run.junit.xml"), junitXml(report, info));
14514
+ if (reporters.has("html")) await writeFile(join(values.out, "wayfarer-run.html"), runReportHtml(report, info));
13335
14515
  return report.results.some(runFailed) ? 1 : 0;
13336
14516
  }
13337
14517
  main(process.argv.slice(2)).then(