apitestcenter 0.7.0 → 0.8.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.
Files changed (49) hide show
  1. package/dist/THIRD-PARTY-NOTICES-UI.txt +1 -1
  2. package/dist/THIRD-PARTY-NOTICES.txt +1 -1
  3. package/dist/cli.mjs +949 -352
  4. package/dist/ui/404/index.html +1 -1
  5. package/dist/ui/404.html +1 -1
  6. package/dist/ui/__next.__PAGE__.txt +9 -9
  7. package/dist/ui/__next._full.txt +10 -10
  8. package/dist/ui/__next._tree.txt +1 -1
  9. package/dist/ui/_next/static/chunks/0e1nwxi9i1ag7.js +1 -0
  10. package/dist/ui/_next/static/chunks/{0txpfp028djv6.js → 11nxnz4doxir5.js} +64 -64
  11. package/dist/ui/_next/static/chunks/2xls_z5abvl-5.js +1 -0
  12. package/dist/ui/_not-found/__next._full.txt +9 -9
  13. package/dist/ui/_not-found/__next._not-found.__PAGE__.txt +8 -8
  14. package/dist/ui/_not-found/__next._tree.txt +1 -1
  15. package/dist/ui/_not-found/index.html +1 -1
  16. package/dist/ui/_not-found/index.txt +9 -9
  17. package/dist/ui/compare/__next._full.txt +10 -10
  18. package/dist/ui/compare/__next._tree.txt +1 -1
  19. package/dist/ui/compare/__next.compare.__PAGE__.txt +9 -9
  20. package/dist/ui/compare/index.html +1 -1
  21. package/dist/ui/compare/index.txt +10 -10
  22. package/dist/ui/history/__next._full.txt +11 -11
  23. package/dist/ui/history/__next._tree.txt +1 -1
  24. package/dist/ui/history/__next.history.__PAGE__.txt +10 -10
  25. package/dist/ui/history/index.html +1 -1
  26. package/dist/ui/history/index.txt +11 -11
  27. package/dist/ui/index.html +1 -1
  28. package/dist/ui/index.txt +10 -10
  29. package/dist/ui/preview/__next._full.txt +11 -11
  30. package/dist/ui/preview/__next._tree.txt +1 -1
  31. package/dist/ui/preview/__next.preview.__PAGE__.txt +10 -10
  32. package/dist/ui/preview/index.html +1 -1
  33. package/dist/ui/preview/index.txt +11 -11
  34. package/dist/ui/suite/__next._full.txt +10 -10
  35. package/dist/ui/suite/__next._tree.txt +1 -1
  36. package/dist/ui/suite/__next.suite.__PAGE__.txt +9 -9
  37. package/dist/ui/suite/index.html +1 -1
  38. package/dist/ui/suite/index.txt +10 -10
  39. package/dist/ui/tests/__next._full.txt +9 -9
  40. package/dist/ui/tests/__next._tree.txt +1 -1
  41. package/dist/ui/tests/__next.tests.__PAGE__.txt +8 -8
  42. package/dist/ui/tests/index.html +1 -1
  43. package/dist/ui/tests/index.txt +9 -9
  44. package/package.json +1 -1
  45. package/dist/ui/_next/static/chunks/0kfr8kv4cj_kv.js +0 -1
  46. package/dist/ui/_next/static/chunks/1w_q4qvvuinj2.js +0 -1
  47. /package/dist/ui/_next/static/{lBYayPKoT6YJhgfHFuw6O → BJz1RrA7HqZwUV22DBTCg}/_buildManifest.js +0 -0
  48. /package/dist/ui/_next/static/{lBYayPKoT6YJhgfHFuw6O → BJz1RrA7HqZwUV22DBTCg}/_clientMiddlewareManifest.js +0 -0
  49. /package/dist/ui/_next/static/{lBYayPKoT6YJhgfHFuw6O → BJz1RrA7HqZwUV22DBTCg}/_ssgManifest.js +0 -0
package/dist/cli.mjs CHANGED
@@ -804,14 +804,41 @@ function safeSegment(text, fallback = "untitled") {
804
804
  return s || fallback;
805
805
  }
806
806
  function defaultOAuth() {
807
- return { grant: "client_credentials", tokenUrl: "", authUrl: "", redirectUri: DEFAULT_REDIRECT_URI, clientId: "", clientSecret: "", scope: "", username: "", password: "", clientAuth: "basic", pkce: true, extra: [] };
807
+ return {
808
+ grant: "client_credentials",
809
+ tokenUrl: "",
810
+ authUrl: "",
811
+ redirectUri: DEFAULT_REDIRECT_URI,
812
+ clientId: "",
813
+ clientSecret: "",
814
+ scope: "",
815
+ username: "",
816
+ password: "",
817
+ clientAuth: "basic",
818
+ pkce: true,
819
+ extra: [],
820
+ privateKey: "",
821
+ assertionAlgorithm: "RS256",
822
+ issuer: "",
823
+ subject: "",
824
+ audience: "",
825
+ kid: "",
826
+ certificate: "",
827
+ extraClaims: "",
828
+ samlSource: "idp",
829
+ idpUrl: "",
830
+ userId: "",
831
+ companyId: "",
832
+ assertion: ""
833
+ };
808
834
  }
809
835
  function oauthFromUnknown(value) {
810
836
  const base = defaultOAuth();
811
837
  if (!value || typeof value !== "object") return base;
812
838
  const v = value;
813
839
  const s = (x, d) => typeof x === "string" ? x : d;
814
- const grants = ["client_credentials", "password", "authorization_code"];
840
+ const grants = ["client_credentials", "password", "authorization_code", "jwt_bearer", "saml_bearer"];
841
+ const algs = ["RS256", "RS384", "RS512", "PS256", "ES256", "ES384"];
815
842
  return {
816
843
  grant: grants.includes(v.grant) ? v.grant : base.grant,
817
844
  tokenUrl: s(v.tokenUrl, ""),
@@ -822,8 +849,21 @@ function oauthFromUnknown(value) {
822
849
  scope: s(v.scope, ""),
823
850
  username: s(v.username, ""),
824
851
  password: s(v.password, ""),
825
- clientAuth: v.clientAuth === "body" ? "body" : "basic",
852
+ clientAuth: v.clientAuth === "body" ? "body" : v.clientAuth === "private_key_jwt" ? "private_key_jwt" : "basic",
826
853
  pkce: v.pkce !== false,
854
+ privateKey: s(v.privateKey, ""),
855
+ assertionAlgorithm: algs.includes(v.assertionAlgorithm) ? v.assertionAlgorithm : "RS256",
856
+ issuer: s(v.issuer, ""),
857
+ subject: s(v.subject, ""),
858
+ audience: s(v.audience, ""),
859
+ kid: s(v.kid, ""),
860
+ certificate: s(v.certificate, ""),
861
+ extraClaims: s(v.extraClaims, ""),
862
+ samlSource: v.samlSource === "assertion" ? "assertion" : "idp",
863
+ idpUrl: s(v.idpUrl, ""),
864
+ userId: s(v.userId, ""),
865
+ companyId: s(v.companyId, ""),
866
+ assertion: s(v.assertion, ""),
827
867
  extra: (Array.isArray(v.extra) ? v.extra : []).flatMap((e) => e && typeof e.key === "string" ? [{ key: e.key, value: s(e.value, "") }] : [])
828
868
  };
829
869
  }
@@ -858,9 +898,28 @@ function jwtFromUnknown(value) {
858
898
  prefix: s(v.prefix, base.prefix)
859
899
  };
860
900
  }
901
+ function defaultOAuth1() {
902
+ return { consumerKey: "", consumerSecret: "", token: "", tokenSecret: "", signatureMethod: "HMAC-SHA256", realm: "", addTo: "header" };
903
+ }
904
+ function oauth1FromUnknown(value) {
905
+ const base = defaultOAuth1();
906
+ if (!value || typeof value !== "object") return base;
907
+ const v = value;
908
+ const s = (x, d) => typeof x === "string" ? x : d;
909
+ const methods = ["HMAC-SHA256", "HMAC-SHA1", "PLAINTEXT"];
910
+ return {
911
+ consumerKey: s(v.consumerKey, ""),
912
+ consumerSecret: s(v.consumerSecret, ""),
913
+ token: s(v.token, ""),
914
+ tokenSecret: s(v.tokenSecret, ""),
915
+ signatureMethod: methods.includes(v.signatureMethod) ? v.signatureMethod : base.signatureMethod,
916
+ realm: s(v.realm, ""),
917
+ addTo: v.addTo === "query" ? "query" : "header"
918
+ };
919
+ }
861
920
  function emptyFolderSettings() {
862
921
  return {
863
- auth: { authMode: "inherit", oauth: defaultOAuth(), aws: defaultAws(), jwt: defaultJwt(), domain: "", token: "", username: "", password: "", apiName: "X-API-Key", apiValue: "", apiIn: "header" },
922
+ auth: { authMode: "inherit", oauth: defaultOAuth(), oauth1: defaultOAuth1(), aws: defaultAws(), jwt: defaultJwt(), domain: "", token: "", username: "", password: "", apiName: "X-API-Key", apiValue: "", apiIn: "header" },
864
923
  headers: [],
865
924
  variables: []
866
925
  };
@@ -871,7 +930,7 @@ function folderFromUnknown(value) {
871
930
  const v = value;
872
931
  const a = v.auth ?? {};
873
932
  const s = (x, d) => typeof x === "string" ? x : d;
874
- const modes = ["inherit", "none", "bearer", "basic", "api_key", "oauth2", "digest", "ntlm", "aws_sigv4", "jwt"];
933
+ const modes = ["inherit", "none", "bearer", "basic", "api_key", "oauth2", "digest", "ntlm", "aws_sigv4", "jwt", "oauth1"];
875
934
  const authMode2 = modes.includes(a.authMode) ? a.authMode : "inherit";
876
935
  const headers = (Array.isArray(v.headers) ? v.headers : []).flatMap(
877
936
  (h) => h && typeof h.key === "string" ? [{ key: h.key, value: s(h.value, "") }] : []
@@ -881,6 +940,7 @@ function folderFromUnknown(value) {
881
940
  auth: {
882
941
  authMode: authMode2,
883
942
  oauth: oauthFromUnknown(a.oauth),
943
+ oauth1: oauth1FromUnknown(a.oauth1),
884
944
  aws: awsFromUnknown(a.aws),
885
945
  jwt: jwtFromUnknown(a.jwt),
886
946
  domain: s(a.domain, ""),
@@ -917,7 +977,7 @@ var init_workspace = __esm({
917
977
  FOLDER_FILE = "_folder.json";
918
978
  MAX_FOLDER_DEPTH = 6;
919
979
  DEFAULT_REDIRECT_URI = "http://127.0.0.1:53682/callback";
920
- NESTED_SECRET_FIELDS = { oauth: ["clientSecret", "password"], aws: ["secretAccessKey", "sessionToken"], jwt: ["secret", "privateKey"] };
980
+ NESTED_SECRET_FIELDS = { oauth: ["clientSecret", "password", "privateKey", "assertion"], aws: ["secretAccessKey", "sessionToken"], jwt: ["secret", "privateKey"], oauth1: ["consumerSecret", "tokenSecret"] };
921
981
  }
922
982
  });
923
983
 
@@ -16307,18 +16367,18 @@ var init_definition = __esm({
16307
16367
  thinkTimeMs: external_exports.number().min(0).default(0),
16308
16368
  thinkJitterMs: external_exports.number().min(0).default(0),
16309
16369
  gracefulStop: durationSchema.default("10s")
16310
- }).strict().superRefine((load2, ctx) => {
16311
- if ((load2.executor === "ramping-vus" || load2.executor === "ramping-arrival-rate") && load2.stages.length === 0) {
16370
+ }).strict().superRefine((load3, ctx) => {
16371
+ if ((load3.executor === "ramping-vus" || load3.executor === "ramping-arrival-rate") && load3.stages.length === 0) {
16312
16372
  ctx.addIssue({
16313
16373
  code: external_exports.ZodIssueCode.custom,
16314
- message: `${load2.executor} requires at least one stage`,
16374
+ message: `${load3.executor} requires at least one stage`,
16315
16375
  path: ["stages"]
16316
16376
  });
16317
16377
  }
16318
- if (load2.executor === "constant-arrival-rate" && !load2.rate) {
16378
+ if (load3.executor === "constant-arrival-rate" && !load3.rate) {
16319
16379
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: "constant-arrival-rate needs a rate", path: ["rate"] });
16320
16380
  }
16321
- if (!isArrivalRate(load2.executor) && load2.stages.some((s) => s.target > 500)) {
16381
+ if (!isArrivalRate(load3.executor) && load3.stages.some((s) => s.target > 500)) {
16322
16382
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: "A stage can use at most 500 virtual users", path: ["stages"] });
16323
16383
  }
16324
16384
  });
@@ -16341,9 +16401,11 @@ var init_definition = __esm({
16341
16401
  maxRedirects: external_exports.number().int().min(0).max(20).default(5),
16342
16402
  httpVersion: external_exports.enum(["1.1", "2"]).default("1.1"),
16343
16403
  tlsSkipVerify: external_exports.boolean().default(false),
16344
- connectionReuse: external_exports.boolean().default(true)
16404
+ connectionReuse: external_exports.boolean().default(true),
16405
+ /** Send cookies from the jar and keep the ones the API sets (like a browser). */
16406
+ cookies: external_exports.boolean().default(true)
16345
16407
  }).strict();
16346
- oauthGrants = ["client_credentials", "password", "authorization_code"];
16408
+ oauthGrants = ["client_credentials", "password", "authorization_code", "jwt_bearer", "saml_bearer"];
16347
16409
  oauthConfigSchema = external_exports.object({
16348
16410
  grant: external_exports.enum(oauthGrants),
16349
16411
  tokenUrl: external_exports.string().url(),
@@ -16355,7 +16417,25 @@ var init_definition = __esm({
16355
16417
  username: external_exports.string().default(""),
16356
16418
  password: external_exports.string().default(""),
16357
16419
  /** How the client id and secret go to the token endpoint. */
16358
- clientAuth: external_exports.enum(["basic", "body"]).default("basic"),
16420
+ /** basic / body: the client secret; private_key_jwt: a JWT signed with the private key (client assertion). */
16421
+ clientAuth: external_exports.enum(["basic", "body", "private_key_jwt"]).default("basic"),
16422
+ // Signed assertions (JWT bearer grant, client assertion).
16423
+ privateKey: external_exports.string().default(""),
16424
+ assertionAlgorithm: external_exports.enum(["RS256", "RS384", "RS512", "PS256", "ES256", "ES384"]).default("RS256"),
16425
+ issuer: external_exports.string().default(""),
16426
+ subject: external_exports.string().default(""),
16427
+ audience: external_exports.string().default(""),
16428
+ kid: external_exports.string().default(""),
16429
+ /** The certificate (PEM) of the key: its thumbprint goes in the JWT header (x5t), as Azure AD wants. */
16430
+ certificate: external_exports.string().default(""),
16431
+ /** More claims for the JWT bearer assertion, as JSON. */
16432
+ extraClaims: external_exports.string().default(""),
16433
+ // SAML 2.0 bearer (SAP SuccessFactors).
16434
+ samlSource: external_exports.enum(["idp", "assertion"]).default("idp"),
16435
+ idpUrl: external_exports.string().default(""),
16436
+ userId: external_exports.string().default(""),
16437
+ companyId: external_exports.string().default(""),
16438
+ assertion: external_exports.string().default(""),
16359
16439
  pkce: external_exports.boolean().default(true),
16360
16440
  /** Extra token-request fields, e.g. audience or resource. */
16361
16441
  extra: external_exports.array(keyValueSchema).max(20).default([])
@@ -16376,6 +16456,17 @@ var init_definition = __esm({
16376
16456
  headerName: external_exports.string().min(1).default("Authorization"),
16377
16457
  prefix: external_exports.string().default("Bearer")
16378
16458
  }).strict(),
16459
+ // OAuth 1.0 (RFC 5849): every request signed with the consumer and token secrets (NetSuite TBA…).
16460
+ external_exports.object({
16461
+ mode: external_exports.literal("oauth1"),
16462
+ consumerKey: external_exports.string().min(1),
16463
+ consumerSecret: external_exports.string(),
16464
+ token: external_exports.string().default(""),
16465
+ tokenSecret: external_exports.string().default(""),
16466
+ signatureMethod: external_exports.enum(["HMAC-SHA256", "HMAC-SHA1", "PLAINTEXT"]).default("HMAC-SHA256"),
16467
+ realm: external_exports.string().default(""),
16468
+ addTo: external_exports.enum(["header", "query"]).default("header")
16469
+ }).strict(),
16379
16470
  // AWS Signature Version 4: every request is signed (API Gateway, S3, Lambda function URLs…).
16380
16471
  external_exports.object({
16381
16472
  mode: external_exports.literal("aws_sigv4"),
@@ -24010,6 +24101,7 @@ function defaultRequestDraft() {
24010
24101
  bodyText: "",
24011
24102
  authMode: "inherit",
24012
24103
  oauth: defaultOAuth(),
24104
+ oauth1: defaultOAuth1(),
24013
24105
  aws: defaultAws(),
24014
24106
  jwt: defaultJwt(),
24015
24107
  domain: "",
@@ -24022,6 +24114,7 @@ function defaultRequestDraft() {
24022
24114
  timeout: "60s",
24023
24115
  maxRedirects: 5,
24024
24116
  connectionReuse: true,
24117
+ useCookies: true,
24025
24118
  tlsSkip: false,
24026
24119
  checks: [],
24027
24120
  before: null,
@@ -24065,7 +24158,7 @@ function requestFromUnknown(value) {
24065
24158
  if (!value || typeof value !== "object") return base;
24066
24159
  const item = value;
24067
24160
  const bodyMode = item.bodyMode === "json" || item.bodyMode === "text" ? item.bodyMode : "none";
24068
- const authMode2 = item.authMode === "bearer" || item.authMode === "basic" || item.authMode === "api_key" || item.authMode === "inherit" || item.authMode === "oauth2" || item.authMode === "digest" || item.authMode === "ntlm" || item.authMode === "aws_sigv4" || item.authMode === "jwt" ? item.authMode : item.authMode === void 0 ? "inherit" : "none";
24161
+ const authMode2 = item.authMode === "bearer" || item.authMode === "basic" || item.authMode === "api_key" || item.authMode === "inherit" || item.authMode === "oauth2" || item.authMode === "digest" || item.authMode === "ntlm" || item.authMode === "aws_sigv4" || item.authMode === "jwt" || item.authMode === "oauth1" ? item.authMode : item.authMode === void 0 ? "inherit" : "none";
24069
24162
  return {
24070
24163
  method: METHODS.includes(item.method) ? item.method : "GET",
24071
24164
  url: str(item.url, ""),
@@ -24077,6 +24170,7 @@ function requestFromUnknown(value) {
24077
24170
  bodyText: str(item.bodyText, ""),
24078
24171
  authMode: authMode2,
24079
24172
  oauth: oauthFromUnknown(item.oauth),
24173
+ oauth1: oauth1FromUnknown(item.oauth1),
24080
24174
  aws: awsFromUnknown(item.aws),
24081
24175
  jwt: jwtFromUnknown(item.jwt),
24082
24176
  domain: str(item.domain, ""),
@@ -24089,6 +24183,7 @@ function requestFromUnknown(value) {
24089
24183
  timeout: str(item.timeout, base.timeout),
24090
24184
  maxRedirects: num(item.maxRedirects, base.maxRedirects),
24091
24185
  connectionReuse: item.connectionReuse !== false,
24186
+ useCookies: item.useCookies !== false,
24092
24187
  tlsSkip: item.tlsSkip === true,
24093
24188
  checks: checksFrom(item.checks),
24094
24189
  before: beforeFrom(item.before),
@@ -24099,12 +24194,12 @@ function requestFromUnknown(value) {
24099
24194
  })
24100
24195
  };
24101
24196
  }
24102
- function applyPreset(load2, preset) {
24103
- if (preset === "custom") return { ...load2, preset };
24104
- return { preset, mode: "users", rps: load2.rps, maxVus: load2.maxVus, stages: null, ...LOAD_KNOB_PRESETS[preset] };
24197
+ function applyPreset(load3, preset) {
24198
+ if (preset === "custom") return { ...load3, preset };
24199
+ return { preset, mode: "users", rps: load3.rps, maxVus: load3.maxVus, stages: null, ...LOAD_KNOB_PRESETS[preset] };
24105
24200
  }
24106
- function toLoadConfig(load2) {
24107
- const { preset: _preset, ...knobs } = load2;
24201
+ function toLoadConfig(load3) {
24202
+ const { preset: _preset, ...knobs } = load3;
24108
24203
  return loadFromLabKnobs(knobs);
24109
24204
  }
24110
24205
  function withUrl(draft, value) {
@@ -24119,12 +24214,12 @@ function hasBody(method) {
24119
24214
  function draftRuntimeNames(draft) {
24120
24215
  return runtimeVariableNames({ setup: draft.before, data: draft.testData });
24121
24216
  }
24122
- function buildPayload(draft, load2, name, targets) {
24217
+ function buildPayload(draft, load3, name, targets) {
24123
24218
  const parts = requestParts(draft);
24124
24219
  return {
24125
24220
  name: name.slice(0, 120),
24126
24221
  ...parts,
24127
- load: load2,
24222
+ load: load3,
24128
24223
  targets,
24129
24224
  setup: void 0,
24130
24225
  data: draft.testData && draft.testData.rows.length ? draft.testData : void 0,
@@ -24178,6 +24273,11 @@ function requestParts(draft) {
24178
24273
  if (!a.accessKeyId.trim() || !a.secretAccessKey) throw new Error("AWS signing needs the access key ID and the secret access key");
24179
24274
  if (!a.region.trim() || !a.service.trim()) throw new Error("AWS signing needs the region (like eu-west-1) and the service (like execute-api or s3)");
24180
24275
  auth = { mode: "aws_sigv4", accessKeyId: a.accessKeyId.trim(), secretAccessKey: a.secretAccessKey, sessionToken: a.sessionToken.trim(), region: a.region.trim(), service: a.service.trim() };
24276
+ } else if (draft.authMode === "oauth1") {
24277
+ const o = draft.oauth1;
24278
+ if (!o.consumerKey.trim()) throw new Error("OAuth 1.0 needs the consumer key");
24279
+ if (o.token.trim() && !o.tokenSecret) throw new Error("OAuth 1.0: the token needs its token secret");
24280
+ auth = { mode: "oauth1", consumerKey: o.consumerKey.trim(), consumerSecret: o.consumerSecret, token: o.token.trim(), tokenSecret: o.tokenSecret, signatureMethod: o.signatureMethod, realm: o.realm.trim(), addTo: o.addTo };
24181
24281
  } else if (draft.authMode === "jwt") {
24182
24282
  const j = draft.jwt;
24183
24283
  const hmac3 = j.algorithm.startsWith("HS");
@@ -24219,7 +24319,8 @@ function requestParts(draft) {
24219
24319
  maxRedirects: draft.maxRedirects,
24220
24320
  httpVersion: "1.1",
24221
24321
  tlsSkipVerify: draft.tlsSkip,
24222
- connectionReuse: draft.connectionReuse
24322
+ connectionReuse: draft.connectionReuse,
24323
+ cookies: draft.useCookies
24223
24324
  }
24224
24325
  };
24225
24326
  }
@@ -24250,22 +24351,22 @@ function durationError(value, allowZero = true) {
24250
24351
  if (!allowZero && /^0+(\.0+)?(ms|s|m|h)$/.test(value.trim())) return "Must be longer than 0";
24251
24352
  return void 0;
24252
24353
  }
24253
- function loadProblem(load2) {
24254
- if (load2.stages && load2.stages.length) {
24255
- for (const [i, stage] of load2.stages.entries()) {
24354
+ function loadProblem(load3) {
24355
+ if (load3.stages && load3.stages.length) {
24356
+ for (const [i, stage] of load3.stages.entries()) {
24256
24357
  const bad = durationError(stage.duration, false);
24257
24358
  if (bad) return `Stage ${i + 1}: ${bad}`;
24258
24359
  if (!(stage.target >= 0)) return `Stage ${i + 1}: target must be 0 or more`;
24259
- if (load2.mode !== "rate" && stage.target > 500) return `Stage ${i + 1}: at most 500 virtual users`;
24360
+ if (load3.mode !== "rate" && stage.target > 500) return `Stage ${i + 1}: at most 500 virtual users`;
24260
24361
  }
24261
- if (!load2.stages.some((s) => s.target > 0)) return "At least one stage needs a target above 0";
24262
- return durationError(load2.gracefulStop) ?? null;
24362
+ if (!load3.stages.some((s) => s.target > 0)) return "At least one stage needs a target above 0";
24363
+ return durationError(load3.gracefulStop) ?? null;
24263
24364
  }
24264
- if (load2.mode === "rate") {
24265
- if (!(load2.rps >= 1 && load2.rps <= 1e5)) return "Requests per second must be 1\u2013100,000";
24266
- if (!(load2.maxVus >= 1 && load2.maxVus <= 2e3)) return "Max virtual users must be 1\u20132,000";
24267
- } else if (!(load2.vus >= 1 && load2.vus <= 500)) return "Virtual users must be 1\u2013500";
24268
- return durationError(load2.duration, false) ?? durationError(load2.rampUp) ?? durationError(load2.rampDown) ?? durationError(load2.gracefulStop) ?? null;
24365
+ if (load3.mode === "rate") {
24366
+ if (!(load3.rps >= 1 && load3.rps <= 1e5)) return "Requests per second must be 1\u2013100,000";
24367
+ if (!(load3.maxVus >= 1 && load3.maxVus <= 2e3)) return "Max virtual users must be 1\u20132,000";
24368
+ } else if (!(load3.vus >= 1 && load3.vus <= 500)) return "Virtual users must be 1\u2013500";
24369
+ return durationError(load3.duration, false) ?? durationError(load3.rampUp) ?? durationError(load3.rampDown) ?? durationError(load3.gracefulStop) ?? null;
24269
24370
  }
24270
24371
  function applyFolders(draft, folders) {
24271
24372
  let next = draft;
@@ -24275,7 +24376,7 @@ function applyFolders(draft, folders) {
24275
24376
  const a = from?.settings.auth;
24276
24377
  if (from && a && a.authMode !== "none") {
24277
24378
  authFrom = from.path;
24278
- next = { ...next, authMode: a.authMode, oauth: a.oauth, aws: a.aws, jwt: a.jwt, domain: a.domain, token: a.token, username: a.username, password: a.password, apiName: a.apiName, apiValue: a.apiValue, apiIn: a.apiIn };
24379
+ next = { ...next, authMode: a.authMode, oauth: a.oauth, oauth1: a.oauth1, aws: a.aws, jwt: a.jwt, domain: a.domain, token: a.token, username: a.username, password: a.password, apiName: a.apiName, apiValue: a.apiValue, apiIn: a.apiIn };
24279
24380
  } else {
24280
24381
  next = { ...next, authMode: "none" };
24281
24382
  }
@@ -24298,6 +24399,20 @@ function oauthWire(o) {
24298
24399
  throw new Error("The redirect URL must be on this computer, like http://127.0.0.1:53682/callback \u2014 add the same one in your OAuth app");
24299
24400
  }
24300
24401
  if (!o.clientId.trim()) throw new Error("OAuth 2.0 needs a client ID");
24402
+ const needsKey = o.grant === "jwt_bearer" || o.clientAuth === "private_key_jwt" || o.grant === "saml_bearer" && o.samlSource === "idp";
24403
+ if (needsKey && !o.privateKey.trim()) throw new Error("OAuth 2.0: this sign-in needs the private key (PEM)");
24404
+ if (o.grant === "jwt_bearer" && o.extraClaims.trim()) {
24405
+ try {
24406
+ const c = JSON.parse(o.extraClaims);
24407
+ if (!c || typeof c !== "object" || Array.isArray(c)) throw new Error("x");
24408
+ } catch {
24409
+ throw new Error("OAuth 2.0: the extra claims must be a JSON object");
24410
+ }
24411
+ }
24412
+ if (o.grant === "saml_bearer" && o.samlSource === "idp" && (!/^https?:\/\//i.test(o.idpUrl.trim()) || !o.userId.trim())) {
24413
+ throw new Error("SAML bearer needs the assertion URL (SuccessFactors: https://<api-server>/oauth/idp) and the user ID");
24414
+ }
24415
+ if (o.grant === "saml_bearer" && o.samlSource === "assertion" && !o.assertion.trim()) throw new Error("SAML bearer needs the assertion (base64)");
24301
24416
  return {
24302
24417
  grant: o.grant,
24303
24418
  tokenUrl,
@@ -24310,7 +24425,20 @@ function oauthWire(o) {
24310
24425
  password: o.password,
24311
24426
  clientAuth: o.clientAuth,
24312
24427
  pkce: o.pkce,
24313
- extra: o.extra.filter((e) => e.key.trim())
24428
+ extra: o.extra.filter((e) => e.key.trim()),
24429
+ privateKey: o.privateKey.trim(),
24430
+ assertionAlgorithm: o.assertionAlgorithm,
24431
+ issuer: o.issuer.trim(),
24432
+ subject: o.subject.trim(),
24433
+ audience: o.audience.trim(),
24434
+ kid: o.kid.trim(),
24435
+ certificate: o.certificate.trim(),
24436
+ extraClaims: o.extraClaims.trim(),
24437
+ samlSource: o.samlSource,
24438
+ idpUrl: o.idpUrl.trim(),
24439
+ userId: o.userId.trim(),
24440
+ companyId: o.companyId.trim(),
24441
+ assertion: o.assertion.trim()
24314
24442
  };
24315
24443
  }
24316
24444
  var METHODS, DEFAULT_LOAD, str, num, TEMPLATED, DURATION;
@@ -24340,6 +24468,7 @@ var init_draft = __esm({
24340
24468
  "apiName",
24341
24469
  "apiValue",
24342
24470
  "oauth",
24471
+ "oauth1",
24343
24472
  "aws",
24344
24473
  "jwt",
24345
24474
  "domain"
@@ -25189,10 +25318,10 @@ function checksRate(r) {
25189
25318
  return passes + fails ? passes / (passes + fails) : null;
25190
25319
  }
25191
25320
  function plannedLoad(r) {
25192
- const load2 = r.definition.load;
25193
- const rate = load2.executor.endsWith("arrival-rate");
25194
- const peak = rate ? load2.rate ?? Math.max(0, ...load2.stages.map((s) => s.target)) : Math.max(0, ...load2.stages.map((s) => s.target));
25195
- const ms3 = load2.stages.reduce((n, s) => n + durationToMs(s.duration), 0);
25321
+ const load3 = r.definition.load;
25322
+ const rate = load3.executor.endsWith("arrival-rate");
25323
+ const peak = rate ? load3.rate ?? Math.max(0, ...load3.stages.map((s) => s.target)) : Math.max(0, ...load3.stages.map((s) => s.target));
25324
+ const ms3 = load3.stages.reduce((n, s) => n + durationToMs(s.duration), 0);
25196
25325
  return { rate, peak, ms: ms3 };
25197
25326
  }
25198
25327
  function fairness(before, after) {
@@ -25473,6 +25602,83 @@ function __atcSign(method, url, headers, body) {
25473
25602
  }
25474
25603
  });
25475
25604
 
25605
+ // ../shared/src/oauth1.ts
25606
+ function pctEncode(s) {
25607
+ return encodeURIComponent(s).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
25608
+ }
25609
+ function decodeForm(s) {
25610
+ try {
25611
+ return decodeURIComponent(s.replace(/\+/g, " "));
25612
+ } catch {
25613
+ return s;
25614
+ }
25615
+ }
25616
+ function formPairs(text) {
25617
+ return text.replace(/^\?/, "").split("&").filter(Boolean).map((p) => {
25618
+ const i = p.indexOf("=");
25619
+ return [decodeForm(i < 0 ? p : p.slice(0, i)), decodeForm(i < 0 ? "" : p.slice(i + 1))];
25620
+ });
25621
+ }
25622
+ function oauth1BaseString(method, url2, params) {
25623
+ const m = /^(https?):\/\/([^/?#]+)([^?#]*)/i.exec(url2);
25624
+ if (!m) throw new Error("OAuth 1.0 needs a full http(s) address");
25625
+ const scheme = m[1].toLowerCase();
25626
+ let host2 = m[2].toLowerCase();
25627
+ if (scheme === "https" && host2.endsWith(":443") || scheme === "http" && host2.endsWith(":80")) host2 = host2.replace(/:\d+$/, "");
25628
+ const base = `${scheme}://${host2}${m[3] || "/"}`;
25629
+ const normalized = params.map(([k, v]) => [pctEncode(k), pctEncode(v)]).sort(([a, av], [b, bv]) => a < b ? -1 : a > b ? 1 : av < bv ? -1 : av > bv ? 1 : 0).map(([k, v]) => `${k}=${v}`).join("&");
25630
+ return [method.toUpperCase(), pctEncode(base), pctEncode(normalized)].join("&");
25631
+ }
25632
+ function oauth1Params(c, nonce, timestamp) {
25633
+ const p = [
25634
+ ["oauth_consumer_key", c.consumerKey],
25635
+ ["oauth_nonce", nonce],
25636
+ ["oauth_signature_method", c.signatureMethod],
25637
+ ["oauth_timestamp", String(timestamp)],
25638
+ ["oauth_version", "1.0"]
25639
+ ];
25640
+ if (c.token) p.push(["oauth_token", c.token]);
25641
+ return p;
25642
+ }
25643
+ function oauth1SigningKey(c) {
25644
+ return `${pctEncode(c.consumerSecret)}&${pctEncode(c.tokenSecret)}`;
25645
+ }
25646
+ function oauth1Header(c, oauth) {
25647
+ const parts = oauth.map(([k, v]) => `${pctEncode(k)}="${pctEncode(v)}"`);
25648
+ return `OAuth ${c.realm ? `realm="${c.realm.replace(/"/g, "")}", ` : ""}${parts.join(", ")}`;
25649
+ }
25650
+ var OAUTH1_JS;
25651
+ var init_oauth1 = __esm({
25652
+ "../shared/src/oauth1.ts"() {
25653
+ "use strict";
25654
+ OAUTH1_JS = `
25655
+ function __atcPct(s) { return encodeURIComponent(s).replace(/[!'()*]/g, function (c) { return "%" + c.charCodeAt(0).toString(16).toUpperCase(); }); }
25656
+ function __atcForm(t) { return t.replace(/^\\?/, "").split("&").filter(Boolean).map(function (p) { var i = p.indexOf("="); var d = function (x) { try { return decodeURIComponent(x.replace(/\\+/g, " ")); } catch (e) { return x; } }; return [d(i < 0 ? p : p.slice(0, i)), d(i < 0 ? "" : p.slice(i + 1))]; }); }
25657
+ function __atcOAuth1(method, url, headers, body) {
25658
+ var c = OAUTH1;
25659
+ var m = /^(https?):\\/\\/([^/?#]+)([^?#]*)(\\?[^#]*)?/i.exec(url);
25660
+ var scheme = m[1].toLowerCase(), host = m[2].toLowerCase();
25661
+ if ((scheme === "https" && /:443$/.test(host)) || (scheme === "http" && /:80$/.test(host))) host = host.replace(/:\\d+$/, "");
25662
+ var nonce = ""; var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
25663
+ for (var i = 0; i < 32; i++) nonce += abc.charAt(Math.floor(Math.random() * abc.length));
25664
+ var oauth = [["oauth_consumer_key", c.consumerKey], ["oauth_nonce", nonce], ["oauth_signature_method", c.signatureMethod], ["oauth_timestamp", String(Math.floor(Date.now() / 1000))], ["oauth_version", "1.0"]];
25665
+ if (c.token) oauth.push(["oauth_token", c.token]);
25666
+ var params = oauth.concat(__atcForm(m[4] || ""));
25667
+ var ct = ""; for (var k in headers) if (k.toLowerCase() === "content-type") ct = String(headers[k]);
25668
+ if (body && ct.indexOf("application/x-www-form-urlencoded") === 0) params = params.concat(__atcForm(body));
25669
+ var norm = params.map(function (p) { return [__atcPct(p[0]), __atcPct(p[1])]; }).sort(function (a, b) { return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0; }).map(function (p) { return p[0] + "=" + p[1]; }).join("&");
25670
+ var baseString = [method.toUpperCase(), __atcPct(scheme + "://" + host + (m[3] || "/")), __atcPct(norm)].join("&");
25671
+ var key = __atcPct(c.consumerSecret) + "&" + __atcPct(c.tokenSecret);
25672
+ var sig = c.signatureMethod === "PLAINTEXT" ? key : crypto.hmac(c.signatureMethod === "HMAC-SHA1" ? "sha1" : "sha256", key, baseString, "base64");
25673
+ oauth.push(["oauth_signature", sig]);
25674
+ if (c.addTo === "query") return url + (m[4] ? "&" : "?") + oauth.map(function (p) { return __atcPct(p[0]) + "=" + __atcPct(p[1]); }).join("&");
25675
+ headers.Authorization = "OAuth " + (c.realm ? 'realm="' + c.realm.replace(/"/g, "") + '", ' : "") + oauth.map(function (p) { return __atcPct(p[0]) + '="' + __atcPct(p[1]) + '"'; }).join(", ");
25676
+ return url;
25677
+ }
25678
+ `;
25679
+ }
25680
+ });
25681
+
25476
25682
  // ../shared/src/export-fields.ts
25477
25683
  function roundN(n, digits = 2) {
25478
25684
  if (!Number.isFinite(n)) return 0;
@@ -26429,6 +26635,7 @@ var init_src = __esm({
26429
26635
  init_before_after();
26430
26636
  init_ci_templates();
26431
26637
  init_sigv4();
26638
+ init_oauth1();
26432
26639
  init_latency_bins();
26433
26640
  init_checks3();
26434
26641
  init_definition();
@@ -26878,21 +27085,21 @@ function k6Thresholds(targets, hasChecks) {
26878
27085
  if (hasChecks && targets.checksRate != null) out.checks = [wrap(`rate>=${targets.checksRate}`)];
26879
27086
  return out;
26880
27087
  }
26881
- function scenarioBlock(load2) {
26882
- const common = { gracefulStop: load2.gracefulStop };
26883
- switch (load2.executor) {
27088
+ function scenarioBlock(load3) {
27089
+ const common = { gracefulStop: load3.gracefulStop };
27090
+ switch (load3.executor) {
26884
27091
  case "constant-vus":
26885
- return { executor: "constant-vus", vus: load2.vus, duration: load2.duration, ...common };
27092
+ return { executor: "constant-vus", vus: load3.vus, duration: load3.duration, ...common };
26886
27093
  case "ramping-vus":
26887
- return { executor: "ramping-vus", startVUs: load2.startVUs ?? 0, stages: load2.stages, ...common };
27094
+ return { executor: "ramping-vus", startVUs: load3.startVUs ?? 0, stages: load3.stages, ...common };
26888
27095
  case "constant-arrival-rate":
26889
27096
  return {
26890
27097
  executor: "constant-arrival-rate",
26891
- rate: load2.rate,
27098
+ rate: load3.rate,
26892
27099
  timeUnit: "1s",
26893
- duration: load2.duration,
26894
- preAllocatedVUs: load2.vus,
26895
- maxVUs: Math.max(load2.vus, load2.maxVUs ?? load2.vus),
27100
+ duration: load3.duration,
27101
+ preAllocatedVUs: load3.vus,
27102
+ maxVUs: Math.max(load3.vus, load3.maxVUs ?? load3.vus),
26896
27103
  ...common
26897
27104
  };
26898
27105
  case "ramping-arrival-rate":
@@ -26900,9 +27107,9 @@ function scenarioBlock(load2) {
26900
27107
  executor: "ramping-arrival-rate",
26901
27108
  startRate: 0,
26902
27109
  timeUnit: "1s",
26903
- preAllocatedVUs: load2.vus,
26904
- maxVUs: Math.max(load2.vus, load2.maxVUs ?? load2.vus),
26905
- stages: load2.stages,
27110
+ preAllocatedVUs: load3.vus,
27111
+ maxVUs: Math.max(load3.vus, load3.maxVUs ?? load3.vus),
27112
+ stages: load3.stages,
26906
27113
  ...common
26907
27114
  };
26908
27115
  }
@@ -26950,13 +27157,20 @@ function generateK6Script(run2, extra2 = {}) {
26950
27157
  }
26951
27158
  if (run2.auth.mode === "bearer") {
26952
27159
  env.ATC_TOKEN = normalizeBearerToken(run2.auth.token);
26953
- if (extra2.oauthRefresh) env.ATC_OAUTH = JSON.stringify(extra2.oauthRefresh);
27160
+ if (extra2.oauthRefresh) {
27161
+ const { bodies, ...plan } = extra2.oauthRefresh;
27162
+ env.ATC_OAUTH = JSON.stringify(bodies ? { ...plan, start: bodies.start, stepSec: bodies.stepSec } : plan);
27163
+ if (bodies) files["oauth.json"] = JSON.stringify(bodies.list);
27164
+ }
26954
27165
  } else if (run2.auth.mode === "basic") {
26955
27166
  env.ATC_BASIC_USER = run2.auth.username;
26956
27167
  env.ATC_BASIC_PASS = run2.auth.password;
26957
27168
  } else if (run2.auth.mode === "api_key" && run2.auth.in === "header") {
26958
27169
  env.ATC_API_NAME = run2.auth.name;
26959
27170
  env.ATC_API_KEY = run2.auth.value;
27171
+ } else if (run2.auth.mode === "oauth1") {
27172
+ const { mode: _mode, ...creds } = run2.auth;
27173
+ env.ATC_OAUTH1 = JSON.stringify(creds);
26960
27174
  } else if (run2.auth.mode === "aws_sigv4") {
26961
27175
  const { mode: _mode, ...creds } = run2.auth;
26962
27176
  env.ATC_AWS = JSON.stringify(creds);
@@ -26990,7 +27204,7 @@ function __atcRow() {
26990
27204
  const script = `import http from "k6/http";
26991
27205
  import { check, sleep } from "k6";
26992
27206
  import encoding from "k6/encoding";
26993
- ${run2.auth.mode === "aws_sigv4" ? `import crypto from "k6/crypto";
27207
+ ${run2.auth.mode === "aws_sigv4" || run2.auth.mode === "oauth1" ? `import crypto from "k6/crypto";
26994
27208
  ` : ""}${data ? `import exec from "k6/execution";
26995
27209
  import { SharedArray } from "k6/data";
26996
27210
  ` : ""}
@@ -26999,18 +27213,23 @@ export const options = Object.assign(${optionsBlock(run2)}, __ENV.ATC_TLS_AUTH ?
26999
27213
  ${CHECK_EVALUATOR_JS}
27000
27214
  ${dataBlock}${run2.auth.mode === "aws_sigv4" ? `
27001
27215
  const AWS = JSON.parse(__ENV.ATC_AWS);
27002
- ${SIGV4_JS}` : ""}
27216
+ ${SIGV4_JS}` : ""}${run2.auth.mode === "oauth1" ? `
27217
+ const OAUTH1 = JSON.parse(__ENV.ATC_OAUTH1);
27218
+ ${OAUTH1_JS}` : ""}
27003
27219
  const CHECKS = JSON.parse(__ENV.ATC_CHECKS || "[]");
27004
27220
  // JWT: tokens signed by the app for the whole run; each is used for its stretch of time.
27005
27221
  const JWT = __ENV.ATC_JWT ? JSON.parse(__ENV.ATC_JWT) : null;
27006
27222
  const JWTS = JWT ? JSON.parse(open("./jwt.json")) : [];
27007
27223
  // OAuth 2.0: each simulated user gets a new token shortly before the current one runs out.
27008
27224
  const OAUTH = __ENV.ATC_OAUTH ? JSON.parse(__ENV.ATC_OAUTH) : null;
27225
+ const OAUTH_BODIES = OAUTH && OAUTH.stepSec ? JSON.parse(open("./oauth.json")) : null;
27009
27226
  let __atcToken = __ENV.ATC_TOKEN || "";
27010
27227
  let __atcExpires = OAUTH ? OAUTH.expiresAt : 0;
27011
27228
  function __atcFreshToken() {
27012
27229
  if (OAUTH && Date.now() > __atcExpires - 30000) {
27013
- const r = http.post(OAUTH.tokenUrl, OAUTH.body, { headers: OAUTH.headers, tags: { name: "OAuth token" } });
27230
+ const body = OAUTH_BODIES ? OAUTH_BODIES[Math.max(0, Math.min(OAUTH_BODIES.length - 1, Math.floor((Date.now() / 1000 - OAUTH.start) / OAUTH.stepSec)))] : OAUTH.body;
27231
+ // responseType: the run may discard bodies to save memory, but the token is in this one.
27232
+ const r = http.post(OAUTH.tokenUrl, body, { headers: OAUTH.headers, responseType: "text", tags: { name: "OAuth token" } });
27014
27233
  let json = null;
27015
27234
  try { json = r.json(); } catch (e) { json = null; }
27016
27235
  if (r.status >= 200 && r.status < 300 && json && json.access_token) {
@@ -27028,12 +27247,14 @@ export default function () {
27028
27247
  ${data ? 'const vars = __atcRow();\n const f = function (text, mode) { return __atcFill(text, vars, mode || "raw"); };' : "const f = function (text) { return text; };"}
27029
27248
  const headers = JSON.parse(__ENV.ATC_HEADERS || "{}");
27030
27249
  for (const key in headers) headers[key] = f(headers[key]);
27250
+ if (__ENV.ATC_COOKIE) headers.Cookie = __ENV.ATC_COOKIE;
27031
27251
  if (__ENV.ATC_TOKEN) headers.Authorization = "Bearer " + f(__atcFreshToken());
27032
27252
  if (__ENV.ATC_BASIC_USER !== undefined) headers.Authorization = "Basic " + encoding.b64encode(f(__ENV.ATC_BASIC_USER) + ":" + f(__ENV.ATC_BASIC_PASS || ""));
27033
27253
  if (__ENV.ATC_API_NAME && __ENV.ATC_API_KEY) headers[f(__ENV.ATC_API_NAME)] = f(__ENV.ATC_API_KEY);
27034
27254
  if (JWT) headers[JWT.headerName] = (JWT.prefix ? JWT.prefix + " " : "") + JWTS[Math.max(0, Math.min(JWTS.length - 1, Math.floor((Date.now() / 1000 - JWT.start) / JWT.stepSec)))];
27035
27255
  const body = __ENV.ATC_BODY ? f(__ENV.ATC_BODY, __ENV.ATC_BODY_FILL) : null;
27036
27256
  let target = f(__ENV.ATC_URL, "url");
27257
+ if (__ENV.ATC_OAUTH1) target = __atcOAuth1(__ENV.ATC_METHOD, target, headers, body);
27037
27258
  if (__ENV.ATC_AWS) __atcSign(__ENV.ATC_METHOD, target, headers, body);
27038
27259
  if (__ENV.ATC_AUTH_KIND) target = target.replace("://", "://" + encodeURIComponent(f(__ENV.ATC_AUTH_USER)) + ":" + encodeURIComponent(f(__ENV.ATC_AUTH_PASS || "")) + "@");
27039
27260
  const res = http.request(__ENV.ATC_METHOD, target, body, {
@@ -27058,17 +27279,18 @@ export default function () {
27058
27279
  `;
27059
27280
  return { script, env, files };
27060
27281
  }
27061
- function loadDurationHintMs(load2) {
27062
- if (load2.stages.length > 0) {
27063
- return load2.stages.reduce((sum, stage) => sum + durationToMs(stage.duration), 0);
27282
+ function loadDurationHintMs(load3) {
27283
+ if (load3.stages.length > 0) {
27284
+ return load3.stages.reduce((sum, stage) => sum + durationToMs(stage.duration), 0);
27064
27285
  }
27065
- return durationToMs(load2.duration);
27286
+ return durationToMs(load3.duration);
27066
27287
  }
27067
27288
  var init_k6_script = __esm({
27068
27289
  "../shared/src/k6-script.ts"() {
27069
27290
  "use strict";
27070
27291
  init_chain();
27071
27292
  init_checks3();
27293
+ init_oauth1();
27072
27294
  init_sigv4();
27073
27295
  init_definition();
27074
27296
  }
@@ -44903,10 +45125,12 @@ var require_lib = __commonJS({
44903
45125
  import { randomUUID as randomUUID2 } from "node:crypto";
44904
45126
  import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
44905
45127
  import { join as join4 } from "node:path";
44906
- import { createSecureContext } from "node:tls";
44907
- function useCertificateStore(dataDir, store2) {
45128
+ import { X509Certificate } from "node:crypto";
45129
+ import * as tlsModule from "node:tls";
45130
+ import { connect as tlsSocket, createSecureContext, rootCertificates } from "node:tls";
45131
+ function useCertificateStore(dataDir, store3) {
44908
45132
  dir = dataDir;
44909
- secrets = store2;
45133
+ secrets = store3;
44910
45134
  }
44911
45135
  function addTemporaryCertificate(host2, material) {
44912
45136
  checkMaterial(material);
@@ -44928,23 +45152,104 @@ function hostMatches(pattern, url2) {
44928
45152
  }
44929
45153
  return name === host2;
44930
45154
  }
44931
- async function readAll() {
44932
- if (!dir) return [];
45155
+ async function readStore() {
45156
+ if (!dir) return { certificates: [], cas: [] };
44933
45157
  try {
44934
45158
  const raw = JSON.parse(await readFile2(join4(dir, "certificates.json"), "utf8"));
44935
- return Array.isArray(raw.certificates) ? raw.certificates.filter((c) => c && typeof c.id === "string" && typeof c.host === "string") : [];
45159
+ return {
45160
+ certificates: Array.isArray(raw.certificates) ? raw.certificates.filter((c) => c && typeof c.id === "string" && typeof c.host === "string") : [],
45161
+ cas: Array.isArray(raw.cas) ? raw.cas.filter((c) => c && typeof c.id === "string" && typeof c.pem === "string") : []
45162
+ };
44936
45163
  } catch {
44937
- return [];
45164
+ return { certificates: [], cas: [] };
44938
45165
  }
44939
45166
  }
44940
- async function writeAll(list) {
45167
+ async function readAll() {
45168
+ return (await readStore()).certificates;
45169
+ }
45170
+ async function writeStore(next) {
44941
45171
  await mkdir2(dir, { recursive: true });
44942
45172
  const file2 = join4(dir, "certificates.json");
44943
45173
  const tmp = `${file2}.${process.pid}.tmp`;
44944
- await writeFile2(tmp, `${JSON.stringify({ version: 1, certificates: list }, null, 2)}
45174
+ await writeFile2(tmp, `${JSON.stringify({ version: 1, ...next }, null, 2)}
44945
45175
  `, "utf8");
44946
45176
  await rename2(tmp, file2);
44947
45177
  }
45178
+ async function writeAll(list) {
45179
+ await writeStore({ ...await readStore(), certificates: list });
45180
+ }
45181
+ async function listCas() {
45182
+ return (await readStore()).cas.map(({ pem: _pem, ...rest }) => rest);
45183
+ }
45184
+ async function addCas(pemText) {
45185
+ const blocks = pemText.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) ?? [];
45186
+ if (!blocks.length) throw new Error("No certificate in that file \u2014 it should contain -----BEGIN CERTIFICATE-----");
45187
+ const store3 = await readStore();
45188
+ for (const pem of blocks) {
45189
+ let x;
45190
+ try {
45191
+ x = new X509Certificate(pem);
45192
+ } catch {
45193
+ throw new Error("A certificate in that file can't be read");
45194
+ }
45195
+ if (!x.ca) throw new Error(`${x.subject.split("\n").find((l) => l.startsWith("CN="))?.slice(3) ?? "That certificate"} is not a certificate authority \u2014 add the CA that issued the server's certificate`);
45196
+ if (store3.cas.some((c) => c.pem.replace(/\s/g, "") === pem.replace(/\s/g, ""))) continue;
45197
+ const cn = x.subject.split("\n").find((l) => l.startsWith("CN="))?.slice(3) ?? x.subject.replace(/\n/g, ", ");
45198
+ store3.cas.push({ id: randomUUID2(), subject: cn, expires: new Date(x.validTo).toISOString(), pem });
45199
+ }
45200
+ await writeStore(store3);
45201
+ casCache = null;
45202
+ return listCas();
45203
+ }
45204
+ async function removeCa(id) {
45205
+ const store3 = await readStore();
45206
+ if (!store3.cas.some((c) => c.id === id)) return false;
45207
+ await writeStore({ ...store3, cas: store3.cas.filter((c) => c.id !== id) });
45208
+ casCache = null;
45209
+ return true;
45210
+ }
45211
+ async function trustedCas() {
45212
+ casCache ??= (await readStore()).cas;
45213
+ return casCache.map((c) => c.pem);
45214
+ }
45215
+ function systemCas() {
45216
+ if (systemCache) return systemCache;
45217
+ const get = tlsModule.getCACertificates;
45218
+ try {
45219
+ systemCache = get ? get("system") : [];
45220
+ } catch {
45221
+ systemCache = [];
45222
+ }
45223
+ return systemCache;
45224
+ }
45225
+ async function trustPool() {
45226
+ const added = await trustedCas();
45227
+ const system = systemCas();
45228
+ return added.length || system.length ? [...rootCertificates, ...system, ...added] : null;
45229
+ }
45230
+ async function needsTrustedCa(target) {
45231
+ const extra2 = await trustedCas();
45232
+ let url2;
45233
+ try {
45234
+ url2 = new URL(target);
45235
+ } catch {
45236
+ return false;
45237
+ }
45238
+ if (!extra2.length || url2.protocol !== "https:") return false;
45239
+ const ok = (ca) => new Promise((resolve5) => {
45240
+ const sock = tlsSocket({ host: url2.hostname, port: Number(url2.port || 443), servername: url2.hostname, ca, timeout: 8e3, rejectUnauthorized: false }, () => {
45241
+ resolve5(sock.authorized);
45242
+ sock.end();
45243
+ });
45244
+ sock.on("error", () => resolve5(false));
45245
+ sock.on("timeout", () => {
45246
+ sock.destroy();
45247
+ resolve5(false);
45248
+ });
45249
+ });
45250
+ const system = systemCas();
45251
+ return !await ok(system.length ? [...rootCertificates, ...system] : void 0) && await ok([...rootCertificates, ...system, ...extra2]);
45252
+ }
44948
45253
  async function listCertificates() {
44949
45254
  return (await readAll()).map(({ cert: _cert, ...summary }) => summary);
44950
45255
  }
@@ -45049,8 +45354,18 @@ async function k6TlsAuth(urls) {
45049
45354
  }
45050
45355
  async function tlsConnect(target, skipVerify = false) {
45051
45356
  const m = await certificateFor(target);
45052
- if (!m && !skipVerify) return void 0;
45053
- return { ...skipVerify ? { rejectUnauthorized: false } : {}, ...m ? { cert: m.cert, key: m.key, pfx: m.pfx, passphrase: m.passphrase } : {} };
45357
+ const pool = await trustPool();
45358
+ if (!m && !skipVerify && !pool) return void 0;
45359
+ return {
45360
+ ...skipVerify ? { rejectUnauthorized: false } : {},
45361
+ // Added to the usual roots, not instead of them.
45362
+ ...pool ? { ca: pool } : {},
45363
+ ...m ? { cert: m.cert, key: m.key, pfx: m.pfx, passphrase: m.passphrase } : {}
45364
+ };
45365
+ }
45366
+ async function caBundle() {
45367
+ const cas = await trustedCas();
45368
+ return cas.length ? [...rootCertificates, ...systemCas(), ...cas].join("\n") : null;
45054
45369
  }
45055
45370
  function registerCertificateRoutes(app2) {
45056
45371
  app2.get("/certificates", async () => listCertificates());
@@ -45067,12 +45382,27 @@ function registerCertificateRoutes(app2) {
45067
45382
  "/certificates/:id",
45068
45383
  async (request3, reply) => await deleteCertificate(request3.params.id) ? { removed: 1 } : reply.code(404).send({ error: "certificate not found" })
45069
45384
  );
45385
+ app2.get("/certificates/cas", async () => listCas());
45386
+ app2.get("/certificates/system", async () => ({ count: systemCas().length }));
45387
+ app2.post("/certificates/cas", async (request3, reply) => {
45388
+ const pem = request3.body?.pem;
45389
+ if (typeof pem !== "string" || pem.length > 2e6) return reply.code(400).send({ error: "Send the CA file's text as pem" });
45390
+ try {
45391
+ return await addCas(pem);
45392
+ } catch (err) {
45393
+ return reply.code(422).send({ error: err instanceof Error ? err.message : String(err) });
45394
+ }
45395
+ });
45396
+ app2.delete(
45397
+ "/certificates/cas/:id",
45398
+ async (request3, reply) => await removeCa(request3.params.id) ? { removed: 1 } : reply.code(404).send({ error: "not found" })
45399
+ );
45070
45400
  app2.get("/certificates/match", async (request3) => {
45071
45401
  const m = await certificateFor(request3.query.url ?? "").catch(() => null);
45072
45402
  return { host: m?.host ?? null };
45073
45403
  });
45074
45404
  }
45075
- var import_node_forge, dir, secrets, extra, saveBody;
45405
+ var import_node_forge, dir, secrets, extra, casCache, systemCache, saveBody;
45076
45406
  var init_certificates = __esm({
45077
45407
  "../../apps/engine/src/certificates.ts"() {
45078
45408
  "use strict";
@@ -45081,6 +45411,8 @@ var init_certificates = __esm({
45081
45411
  dir = "";
45082
45412
  secrets = null;
45083
45413
  extra = [];
45414
+ casCache = null;
45415
+ systemCache = null;
45084
45416
  saveBody = external_exports.object({
45085
45417
  id: external_exports.string().optional(),
45086
45418
  host: external_exports.string().min(1).max(253),
@@ -45094,6 +45426,113 @@ var init_certificates = __esm({
45094
45426
  }
45095
45427
  });
45096
45428
 
45429
+ // ../../apps/engine/src/cookies.ts
45430
+ function useCookieStore(secrets2) {
45431
+ store = secrets2;
45432
+ jar = null;
45433
+ }
45434
+ async function load() {
45435
+ if (jar) return jar;
45436
+ try {
45437
+ const raw = store ? await store.get(KEY) : null;
45438
+ jar = raw ? JSON.parse(raw).filter((c) => c.expires !== null) : [];
45439
+ } catch {
45440
+ jar = [];
45441
+ }
45442
+ return jar;
45443
+ }
45444
+ async function save(next) {
45445
+ const now = Date.now();
45446
+ jar = next.filter((c) => c.expires === null || c.expires > now).slice(-MAX);
45447
+ await store?.set(KEY, JSON.stringify(jar));
45448
+ }
45449
+ function defaultPath(url2) {
45450
+ const p = url2.pathname;
45451
+ if (!p.startsWith("/") || p.lastIndexOf("/") === 0) return "/";
45452
+ return p.slice(0, p.lastIndexOf("/"));
45453
+ }
45454
+ function parseSetCookie(header, url2, now = Date.now()) {
45455
+ const [pair, ...attrs] = header.split(";");
45456
+ const eq = pair.indexOf("=");
45457
+ if (eq <= 0) return null;
45458
+ const host2 = url2.hostname.toLowerCase();
45459
+ const cookie = { name: pair.slice(0, eq).trim(), value: pair.slice(eq + 1).trim(), domain: host2, includeSubdomains: false, path: defaultPath(url2), expires: null, secure: false, httpOnly: false };
45460
+ let maxAge = null;
45461
+ for (const raw of attrs) {
45462
+ const i = raw.indexOf("=");
45463
+ const k = (i < 0 ? raw : raw.slice(0, i)).trim().toLowerCase();
45464
+ const v = i < 0 ? "" : raw.slice(i + 1).trim();
45465
+ if (k === "domain" && v) {
45466
+ const d = v.replace(/^\./, "").toLowerCase();
45467
+ if (host2 !== d && !host2.endsWith(`.${d}`)) return null;
45468
+ cookie.domain = d;
45469
+ cookie.includeSubdomains = true;
45470
+ } else if (k === "path" && v.startsWith("/")) cookie.path = v;
45471
+ else if (k === "expires") {
45472
+ const t = Date.parse(v);
45473
+ if (Number.isFinite(t)) cookie.expires = t;
45474
+ } else if (k === "max-age" && /^-?\d+$/.test(v)) maxAge = Number(v);
45475
+ else if (k === "secure") cookie.secure = true;
45476
+ else if (k === "httponly") cookie.httpOnly = true;
45477
+ else if (k === "samesite") cookie.sameSite = v;
45478
+ }
45479
+ if (maxAge !== null) cookie.expires = now + maxAge * 1e3;
45480
+ return cookie;
45481
+ }
45482
+ function matches(c, url2, now) {
45483
+ const host2 = url2.hostname.toLowerCase();
45484
+ const domainOk = c.includeSubdomains ? host2 === c.domain || host2.endsWith(`.${c.domain}`) : host2 === c.domain;
45485
+ const path = url2.pathname || "/";
45486
+ const pathOk = path === c.path || path.startsWith(c.path) && (c.path.endsWith("/") || path[c.path.length] === "/");
45487
+ return domainOk && pathOk && (!c.secure || url2.protocol === "https:") && (c.expires === null || c.expires > now);
45488
+ }
45489
+ async function cookieHeader(target) {
45490
+ let url2;
45491
+ try {
45492
+ url2 = typeof target === "string" ? new URL(target) : target;
45493
+ } catch {
45494
+ return "";
45495
+ }
45496
+ const now = Date.now();
45497
+ return (await load()).filter((c) => matches(c, url2, now)).sort((a, b) => b.path.length - a.path.length).map((c) => `${c.name}=${c.value}`).join("; ");
45498
+ }
45499
+ async function storeCookies(target, setCookie) {
45500
+ if (!setCookie || Array.isArray(setCookie) && !setCookie.length) return;
45501
+ const url2 = typeof target === "string" ? new URL(target) : target;
45502
+ const now = Date.now();
45503
+ let next = [...await load()];
45504
+ for (const header of Array.isArray(setCookie) ? setCookie : [setCookie]) {
45505
+ const c = parseSetCookie(header, url2, now);
45506
+ if (!c) continue;
45507
+ next = next.filter((o) => !(o.name === c.name && o.domain === c.domain && o.path === c.path));
45508
+ if (c.expires === null || c.expires > now) next.push(c);
45509
+ }
45510
+ await save(next);
45511
+ }
45512
+ async function listCookies() {
45513
+ const now = Date.now();
45514
+ return (await load()).filter((c) => c.expires === null || c.expires > now);
45515
+ }
45516
+ function registerCookieRoutes(app2) {
45517
+ app2.get("/cookies", async () => (await listCookies()).map((c) => ({ ...c, value: c.value.length > 12 ? `${c.value.slice(0, 6)}\u2026${c.value.slice(-3)}` : c.value })));
45518
+ app2.post("/cookies/delete", async (request3) => {
45519
+ const b = request3.body ?? {};
45520
+ const list = await load();
45521
+ await save(b.all ? [] : b.domainOnly ? list.filter((c) => c.domain !== b.domainOnly) : list.filter((c) => !(c.name === b.name && c.domain === b.domain && c.path === b.path)));
45522
+ return { left: (await listCookies()).length };
45523
+ });
45524
+ }
45525
+ var KEY, MAX, store, jar;
45526
+ var init_cookies = __esm({
45527
+ "../../apps/engine/src/cookies.ts"() {
45528
+ "use strict";
45529
+ KEY = "cookies";
45530
+ MAX = 500;
45531
+ store = null;
45532
+ jar = null;
45533
+ }
45534
+ });
45535
+
45097
45536
  // ../../apps/engine/src/jwt.ts
45098
45537
  import { createHmac, createPrivateKey, sign as cryptoSign, constants as constants2 } from "node:crypto";
45099
45538
  function privateKey(pem) {
@@ -45103,7 +45542,7 @@ function privateKey(pem) {
45103
45542
  throw new Error(`the private key can't be read (${err instanceof Error ? err.message : "bad PEM"}) \u2014 paste the whole PEM, from -----BEGIN to -----END`);
45104
45543
  }
45105
45544
  }
45106
- function signJwt(c, now = Math.floor(Date.now() / 1e3)) {
45545
+ function signJwt(c, now = Math.floor(Date.now() / 1e3), headerExtra = {}) {
45107
45546
  let claims;
45108
45547
  try {
45109
45548
  claims = JSON.parse(c.payload || "{}");
@@ -45113,6 +45552,7 @@ function signJwt(c, now = Math.floor(Date.now() / 1e3)) {
45113
45552
  const body = { iat: now, exp: now + c.lifetimeSec, ...claims };
45114
45553
  const header = { alg: c.algorithm, typ: "JWT" };
45115
45554
  if (c.kid) header.kid = c.kid;
45555
+ Object.assign(header, headerExtra);
45116
45556
  const input = `${b64url(JSON.stringify(header))}.${b64url(JSON.stringify(body))}`;
45117
45557
  const bits = c.algorithm.slice(2);
45118
45558
  const hash2 = `sha${bits}`;
@@ -59388,13 +59828,13 @@ var require_mock_call_history = __commonJS({
59388
59828
  "use strict";
59389
59829
  var { kMockCallHistoryAddLog } = require_mock_symbols();
59390
59830
  var { InvalidArgumentError } = require_errors2();
59391
- function handleFilterCallsWithOptions(criteria, options, handler, store2, allLogs) {
59831
+ function handleFilterCallsWithOptions(criteria, options, handler, store3, allLogs) {
59392
59832
  switch (options.operator) {
59393
59833
  case "OR":
59394
- store2.push(...handler(criteria, allLogs));
59395
- return store2;
59834
+ store3.push(...handler(criteria, allLogs));
59835
+ return store3;
59396
59836
  case "AND":
59397
- return handler(criteria, store2);
59837
+ return handler(criteria, store3);
59398
59838
  default:
59399
59839
  throw new InvalidArgumentError("options.operator must to be a case insensitive string equal to 'OR' or 'AND'");
59400
59840
  }
@@ -62049,12 +62489,12 @@ var require_cache = __commonJS({
62049
62489
  }
62050
62490
  return false;
62051
62491
  }
62052
- function assertCacheStore(store2, name = "CacheStore") {
62053
- if (typeof store2 !== "object" || store2 === null) {
62054
- throw new TypeError(`expected type of ${name} to be a CacheStore, got ${store2 === null ? "null" : typeof store2}`);
62492
+ function assertCacheStore(store3, name = "CacheStore") {
62493
+ if (typeof store3 !== "object" || store3 === null) {
62494
+ throw new TypeError(`expected type of ${name} to be a CacheStore, got ${store3 === null ? "null" : typeof store3}`);
62055
62495
  }
62056
62496
  for (const fn of ["get", "createWriteStream", "delete"]) {
62057
- if (typeof store2[fn] !== "function") {
62497
+ if (typeof store3[fn] !== "function") {
62058
62498
  throw new TypeError(`${name} needs to have a \`${fn}()\` function`);
62059
62499
  }
62060
62500
  }
@@ -62677,15 +63117,15 @@ var require_cache_handler = __commonJS({
62677
63117
  }
62678
63118
  return locationUrl.pathname + locationUrl.search;
62679
63119
  }
62680
- function deleteCachedUri(store2, cacheKey, path) {
62681
- deleteCachedValue(store2, {
63120
+ function deleteCachedUri(store3, cacheKey, path) {
63121
+ deleteCachedValue(store3, {
62682
63122
  ...cacheKey,
62683
63123
  path
62684
63124
  });
62685
63125
  for (let i = 0; i < util.safeHTTPMethods.length; i++) {
62686
63126
  const method = util.safeHTTPMethods[i];
62687
63127
  if (method !== cacheKey.method) {
62688
- deleteCachedValue(store2, {
63128
+ deleteCachedValue(store3, {
62689
63129
  ...cacheKey,
62690
63130
  method,
62691
63131
  path
@@ -62693,7 +63133,7 @@ var require_cache_handler = __commonJS({
62693
63133
  }
62694
63134
  }
62695
63135
  }
62696
- function deleteLocationTargets(store2, cacheKey, headerValue) {
63136
+ function deleteLocationTargets(store3, cacheKey, headerValue) {
62697
63137
  if (headerValue === void 0) {
62698
63138
  return;
62699
63139
  }
@@ -62701,14 +63141,14 @@ var require_cache_handler = __commonJS({
62701
63141
  for (let i = 0; i < values.length; i++) {
62702
63142
  const path = getSameOriginPath(cacheKey, values[i]);
62703
63143
  if (path !== void 0) {
62704
- deleteCachedUri(store2, cacheKey, path);
63144
+ deleteCachedUri(store3, cacheKey, path);
62705
63145
  }
62706
63146
  }
62707
63147
  }
62708
- function invalidateUnsafeRequest(store2, cacheKey, resHeaders) {
62709
- deleteCachedUri(store2, cacheKey, cacheKey.path);
62710
- deleteLocationTargets(store2, cacheKey, resHeaders.location);
62711
- deleteLocationTargets(store2, cacheKey, resHeaders["content-location"]);
63148
+ function invalidateUnsafeRequest(store3, cacheKey, resHeaders) {
63149
+ deleteCachedUri(store3, cacheKey, cacheKey.path);
63150
+ deleteLocationTargets(store3, cacheKey, resHeaders.location);
63151
+ deleteLocationTargets(store3, cacheKey, resHeaders["content-location"]);
62712
63152
  }
62713
63153
  var CacheHandler = class {
62714
63154
  /**
@@ -62740,8 +63180,8 @@ var require_cache_handler = __commonJS({
62740
63180
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
62741
63181
  * @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
62742
63182
  */
62743
- constructor({ store: store2, type, cacheByDefault }, cacheKey, handler) {
62744
- this.#store = store2;
63183
+ constructor({ store: store3, type, cacheByDefault }, cacheKey, handler) {
63184
+ this.#store = store3;
62745
63185
  this.#cacheType = type;
62746
63186
  this.#cacheByDefault = cacheByDefault;
62747
63187
  this.#cacheKey = cacheKey;
@@ -62935,15 +63375,15 @@ var require_cache_handler = __commonJS({
62935
63375
  this.#handler.onResponseError?.(controller, err);
62936
63376
  }
62937
63377
  };
62938
- function deleteCachedValue(store2, cacheKey) {
63378
+ function deleteCachedValue(store3, cacheKey) {
62939
63379
  try {
62940
- store2.delete(cacheKey)?.catch?.(noop);
63380
+ store3.delete(cacheKey)?.catch?.(noop);
62941
63381
  } catch {
62942
63382
  }
62943
63383
  }
62944
- function deleteCachedValueIfNotModified(statusCode, store2, cacheKey) {
63384
+ function deleteCachedValueIfNotModified(statusCode, store3, cacheKey) {
62945
63385
  if (statusCode === 304) {
62946
- deleteCachedValue(store2, cacheKey);
63386
+ deleteCachedValue(store3, cacheKey);
62947
63387
  }
62948
63388
  }
62949
63389
  function revalidationResponseDisallowsCachedReuse(cacheType, resHeaders, cacheControlDirectives) {
@@ -63226,7 +63666,7 @@ var require_memory_cache_store = __commonJS({
63226
63666
  assertCacheKey(key);
63227
63667
  assertCacheValue(val);
63228
63668
  const topLevelKey = `${key.origin}:${key.path}`;
63229
- const store2 = this;
63669
+ const store3 = this;
63230
63670
  const entry = { ...key, ...val, body: [], size: 0 };
63231
63671
  return new Writable({
63232
63672
  write(chunk, encoding, callback) {
@@ -63234,7 +63674,7 @@ var require_memory_cache_store = __commonJS({
63234
63674
  chunk = Buffer.from(chunk, encoding);
63235
63675
  }
63236
63676
  entry.size += chunk.byteLength;
63237
- if (entry.size > store2.#maxEntrySize) {
63677
+ if (entry.size > store3.#maxEntrySize) {
63238
63678
  this.destroy();
63239
63679
  } else {
63240
63680
  entry.body.push(chunk);
@@ -63242,42 +63682,42 @@ var require_memory_cache_store = __commonJS({
63242
63682
  callback(null);
63243
63683
  },
63244
63684
  final(callback) {
63245
- let entries = store2.#entries.get(topLevelKey);
63685
+ let entries = store3.#entries.get(topLevelKey);
63246
63686
  if (!entries) {
63247
63687
  entries = [];
63248
- store2.#entries.set(topLevelKey, entries);
63688
+ store3.#entries.set(topLevelKey, entries);
63249
63689
  }
63250
63690
  const previousEntry = findEntry(key, entries, Date.now());
63251
63691
  if (previousEntry) {
63252
63692
  const index = entries.indexOf(previousEntry);
63253
63693
  entries.splice(index, 1, entry);
63254
- store2.#size -= previousEntry.size;
63694
+ store3.#size -= previousEntry.size;
63255
63695
  } else {
63256
63696
  entries.push(entry);
63257
- store2.#count += 1;
63258
- }
63259
- store2.#size += entry.size;
63260
- if (store2.#size > store2.#maxSize || store2.#count > store2.#maxCount) {
63261
- if (!store2.#hasEmittedMaxSizeEvent) {
63262
- store2.emit("maxSizeExceeded", {
63263
- size: store2.#size,
63264
- maxSize: store2.#maxSize,
63265
- count: store2.#count,
63266
- maxCount: store2.#maxCount
63697
+ store3.#count += 1;
63698
+ }
63699
+ store3.#size += entry.size;
63700
+ if (store3.#size > store3.#maxSize || store3.#count > store3.#maxCount) {
63701
+ if (!store3.#hasEmittedMaxSizeEvent) {
63702
+ store3.emit("maxSizeExceeded", {
63703
+ size: store3.#size,
63704
+ maxSize: store3.#maxSize,
63705
+ count: store3.#count,
63706
+ maxCount: store3.#maxCount
63267
63707
  });
63268
- store2.#hasEmittedMaxSizeEvent = true;
63708
+ store3.#hasEmittedMaxSizeEvent = true;
63269
63709
  }
63270
- for (const [key2, entries2] of store2.#entries) {
63710
+ for (const [key2, entries2] of store3.#entries) {
63271
63711
  for (const entry2 of entries2.splice(0, entries2.length / 2)) {
63272
- store2.#size -= entry2.size;
63273
- store2.#count -= 1;
63712
+ store3.#size -= entry2.size;
63713
+ store3.#count -= 1;
63274
63714
  }
63275
63715
  if (entries2.length === 0) {
63276
- store2.#entries.delete(key2);
63716
+ store3.#entries.delete(key2);
63277
63717
  }
63278
63718
  }
63279
- if (store2.#size < store2.#maxSize && store2.#count < store2.#maxCount) {
63280
- store2.#hasEmittedMaxSizeEvent = false;
63719
+ if (store3.#size < store3.#maxSize && store3.#count < store3.#maxCount) {
63720
+ store3.#hasEmittedMaxSizeEvent = false;
63281
63721
  }
63282
63722
  }
63283
63723
  callback(null);
@@ -63524,9 +63964,9 @@ var require_cache2 = __commonJS({
63524
63964
  function revalidationResponseUpdatesCacheControl(headers) {
63525
63965
  return headers["cache-control"] !== void 0;
63526
63966
  }
63527
- function deleteCachedValue(store2, cacheKey) {
63967
+ function deleteCachedValue(store3, cacheKey) {
63528
63968
  try {
63529
- store2.delete(cacheKey)?.catch?.(nop);
63969
+ store3.delete(cacheKey)?.catch?.(nop);
63530
63970
  } catch {
63531
63971
  }
63532
63972
  }
@@ -63761,7 +64201,7 @@ var require_cache2 = __commonJS({
63761
64201
  }
63762
64202
  module.exports = (opts = {}) => {
63763
64203
  const {
63764
- store: store2 = new MemoryCacheStore(),
64204
+ store: store3 = new MemoryCacheStore(),
63765
64205
  methods = ["GET"],
63766
64206
  cacheByDefault = void 0,
63767
64207
  type = "shared",
@@ -63770,7 +64210,7 @@ var require_cache2 = __commonJS({
63770
64210
  if (typeof opts !== "object" || opts === null) {
63771
64211
  throw new TypeError(`expected type of opts to be an Object, got ${opts === null ? "null" : typeof opts}`);
63772
64212
  }
63773
- assertCacheStore(store2, "opts.store");
64213
+ assertCacheStore(store3, "opts.store");
63774
64214
  assertCacheMethods(methods, "opts.methods");
63775
64215
  assertCacheOrigins(origins, "opts.origins");
63776
64216
  if (typeof cacheByDefault !== "undefined" && typeof cacheByDefault !== "number") {
@@ -63780,7 +64220,7 @@ var require_cache2 = __commonJS({
63780
64220
  throw new TypeError(`expected opts.type to be shared, private, or undefined, got ${typeof type}`);
63781
64221
  }
63782
64222
  const globalOpts = {
63783
- store: store2,
64223
+ store: store3,
63784
64224
  methods,
63785
64225
  cacheByDefault,
63786
64226
  type
@@ -63828,7 +64268,7 @@ var require_cache2 = __commonJS({
63828
64268
  return dispatch(opts2, handler);
63829
64269
  }
63830
64270
  const cacheKey = makeCacheKey(opts2);
63831
- const result = store2.get(cacheKey);
64271
+ const result = store3.get(cacheKey);
63832
64272
  if (result && typeof result.then === "function") {
63833
64273
  return result.then((result2) => handleResult(
63834
64274
  dispatch,
@@ -64791,12 +65231,12 @@ var require_sqlite_cache_store = __commonJS({
64791
65231
  assertCacheValue(value);
64792
65232
  let size = 0;
64793
65233
  const body = [];
64794
- const store2 = this;
65234
+ const store3 = this;
64795
65235
  return new Writable({
64796
65236
  decodeStrings: true,
64797
65237
  write(chunk, encoding, callback) {
64798
65238
  size += chunk.byteLength;
64799
- if (size <= store2.#maxEntrySize) {
65239
+ if (size <= store3.#maxEntrySize) {
64800
65240
  body.push(chunk);
64801
65241
  } else {
64802
65242
  this.destroy();
@@ -64804,7 +65244,7 @@ var require_sqlite_cache_store = __commonJS({
64804
65244
  callback();
64805
65245
  },
64806
65246
  final(callback) {
64807
- store2.set(key, { ...value, body });
65247
+ store3.set(key, { ...value, body });
64808
65248
  callback();
64809
65249
  }
64810
65250
  });
@@ -64868,17 +65308,17 @@ var require_sqlite_cache_store = __commonJS({
64868
65308
  if (now >= value.deleteAt && !canBeExpired) {
64869
65309
  continue;
64870
65310
  }
64871
- let matches = true;
65311
+ let matches2 = true;
64872
65312
  if (value.vary) {
64873
65313
  const vary = JSON.parse(value.vary);
64874
65314
  for (const header in vary) {
64875
65315
  if (!headerValueEquals(headers[header], vary[header])) {
64876
- matches = false;
65316
+ matches2 = false;
64877
65317
  break;
64878
65318
  }
64879
65319
  }
64880
65320
  }
64881
- if (matches) {
65321
+ if (matches2) {
64882
65322
  return value;
64883
65323
  }
64884
65324
  }
@@ -68771,7 +69211,7 @@ var require_parse = __commonJS({
68771
69211
  var { maxNameValuePairSize, maxAttributeValueSize } = require_constants4();
68772
69212
  var { isCTLExcludingHtab } = require_util5();
68773
69213
  var assert2 = __require("node:assert");
68774
- function parseSetCookie(header) {
69214
+ function parseSetCookie2(header) {
68775
69215
  if (isCTLExcludingHtab(header)) {
68776
69216
  return null;
68777
69217
  }
@@ -68895,7 +69335,7 @@ var require_parse = __commonJS({
68895
69335
  return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList);
68896
69336
  }
68897
69337
  module.exports = {
68898
- parseSetCookie,
69338
+ parseSetCookie: parseSetCookie2,
68899
69339
  parseUnparsedAttributes
68900
69340
  };
68901
69341
  }
@@ -68905,7 +69345,7 @@ var require_parse = __commonJS({
68905
69345
  var require_cookies = __commonJS({
68906
69346
  "../../node_modules/.pnpm/undici@8.10.0/node_modules/undici/lib/web/cookies/index.js"(exports, module) {
68907
69347
  "use strict";
68908
- var { parseSetCookie } = require_parse();
69348
+ var { parseSetCookie: parseSetCookie2 } = require_parse();
68909
69349
  var { stringify } = require_util5();
68910
69350
  var { webidl } = require_webidl();
68911
69351
  var { Headers } = require_headers();
@@ -68944,11 +69384,11 @@ var require_cookies = __commonJS({
68944
69384
  if (!cookies) {
68945
69385
  return [];
68946
69386
  }
68947
- return cookies.map((pair) => parseSetCookie(pair));
69387
+ return cookies.map((pair) => parseSetCookie2(pair));
68948
69388
  }
68949
69389
  function parseCookie(cookie) {
68950
69390
  cookie = webidl.converters.DOMString(cookie);
68951
- return parseSetCookie(cookie);
69391
+ return parseSetCookie2(cookie);
68952
69392
  }
68953
69393
  function setCookie(headers, cookie) {
68954
69394
  webidl.argumentLengthCheck(arguments, 2, "setCookie");
@@ -72104,19 +72544,19 @@ ${captureLines}` : capture.stack;
72104
72544
  });
72105
72545
 
72106
72546
  // ../../apps/engine/src/oauth.ts
72107
- import { createHash as createHash2, randomBytes, randomUUID as randomUUID3 } from "node:crypto";
72547
+ import { X509Certificate as X509Certificate2, createHash as createHash2, randomBytes, randomUUID as randomUUID3 } from "node:crypto";
72108
72548
  import { createServer } from "node:http";
72109
72549
  function useOAuthStore(secrets2) {
72110
- store = secrets2;
72550
+ store2 = secrets2;
72111
72551
  }
72112
72552
  function oauthKey(c) {
72113
72553
  const id = JSON.stringify([c.grant, c.tokenUrl, c.authUrl, c.clientId, c.clientSecret, c.scope, c.username, c.password, [...c.extra].map((e) => `${e.key}=${e.value}`).sort()]);
72114
72554
  return createHash2("sha256").update(id).digest("hex").slice(0, 32);
72115
72555
  }
72116
- async function load(key) {
72556
+ async function load2(key) {
72117
72557
  const hit = memory.get(key);
72118
72558
  if (hit) return hit;
72119
- const raw = await store?.get(`oauth:${key}`);
72559
+ const raw = await store2?.get(`oauth:${key}`);
72120
72560
  if (!raw) return null;
72121
72561
  try {
72122
72562
  const token = JSON.parse(raw);
@@ -72126,21 +72566,54 @@ async function load(key) {
72126
72566
  return null;
72127
72567
  }
72128
72568
  }
72129
- async function save(key, token) {
72569
+ async function save2(key, token) {
72130
72570
  memory.set(key, token);
72131
- await store?.set(`oauth:${key}`, JSON.stringify(token));
72571
+ await store2?.set(`oauth:${key}`, JSON.stringify(token));
72132
72572
  }
72133
72573
  async function clearToken(c) {
72134
72574
  const key = oauthKey(c);
72135
72575
  memory.delete(key);
72136
- await store?.set(`oauth:${key}`, "");
72576
+ await store2?.set(`oauth:${key}`, "");
72577
+ }
72578
+ function thumbprint(pem) {
72579
+ if (!pem.trim()) return void 0;
72580
+ try {
72581
+ return Buffer.from(new X509Certificate2(pem).fingerprint.replace(/:/g, ""), "hex").toString("base64url");
72582
+ } catch {
72583
+ throw new OAuthError("the certificate can't be read \u2014 paste the whole PEM, from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE-----");
72584
+ }
72137
72585
  }
72138
- function tokenRequest(c, fields) {
72586
+ function signedAssertion(c, claims, now) {
72587
+ const x5t = thumbprint(c.certificate);
72588
+ try {
72589
+ return signJwt(
72590
+ { algorithm: c.assertionAlgorithm, key: c.privateKey, payload: JSON.stringify({ jti: randomUUID3(), ...claims }), lifetimeSec: 300, kid: c.kid, headerName: "", prefix: "" },
72591
+ now,
72592
+ x5t ? { x5t } : {}
72593
+ );
72594
+ } catch (err) {
72595
+ throw new OAuthError(err instanceof Error ? err.message : String(err));
72596
+ }
72597
+ }
72598
+ function extraClaims(c) {
72599
+ if (!c.extraClaims) return {};
72600
+ try {
72601
+ return JSON.parse(c.extraClaims);
72602
+ } catch {
72603
+ throw new OAuthError("the extra claims are not valid JSON");
72604
+ }
72605
+ }
72606
+ function tokenRequest(c, fields, now = Math.floor(Date.now() / 1e3)) {
72139
72607
  const form = new URLSearchParams(fields);
72140
72608
  if (c.scope && !form.has("scope") && fields.grant_type !== "authorization_code") form.set("scope", c.scope);
72141
72609
  for (const e of c.extra) if (e.key.trim()) form.set(e.key.trim(), e.value);
72142
72610
  const headers = { "content-type": "application/x-www-form-urlencoded", accept: "application/json" };
72143
- if (c.clientAuth === "basic" && c.clientSecret) {
72611
+ if (c.clientAuth === "private_key_jwt") {
72612
+ form.set("client_id", c.clientId);
72613
+ form.set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
72614
+ form.set("client_assertion", signedAssertion(c, { iss: c.clientId, sub: c.clientId, aud: c.audience || c.tokenUrl }, now));
72615
+ } else if (fields.grant_type === JWT_BEARER && !c.clientSecret) {
72616
+ } else if (c.clientAuth === "basic" && c.clientSecret) {
72144
72617
  headers.authorization = `Basic ${Buffer.from(`${encodeURIComponent(c.clientId)}:${encodeURIComponent(c.clientSecret)}`).toString("base64")}`;
72145
72618
  } else {
72146
72619
  form.set("client_id", c.clientId);
@@ -72182,39 +72655,84 @@ async function postToken(c, fields) {
72182
72655
  obtainedAt: Date.now()
72183
72656
  };
72184
72657
  }
72185
- function grantFields(c) {
72658
+ async function grantFields(c, now = Math.floor(Date.now() / 1e3)) {
72186
72659
  if (c.grant === "client_credentials") return { grant_type: "client_credentials" };
72187
72660
  if (c.grant === "password") return { grant_type: "password", username: c.username, password: c.password };
72661
+ if (c.grant === "jwt_bearer") {
72662
+ const claims = { iss: c.issuer || c.clientId, ...c.subject ? { sub: c.subject } : {}, aud: c.audience || c.tokenUrl, ...extraClaims(c) };
72663
+ return { grant_type: JWT_BEARER, assertion: signedAssertion(c, claims, now) };
72664
+ }
72665
+ if (c.grant === "saml_bearer") {
72666
+ const fields = { grant_type: SAML_BEARER, assertion: c.samlSource === "assertion" ? c.assertion : await samlFromIdp(c) };
72667
+ if (c.companyId) fields.company_id = c.companyId;
72668
+ return fields;
72669
+ }
72188
72670
  throw new OAuthError("sign in first \u2014 press \u201CSign in in the browser\u201D on the Auth tab; the token is then kept and refreshed");
72189
72671
  }
72672
+ async function samlFromIdp(c) {
72673
+ const key = c.privateKey.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
72674
+ const form = new URLSearchParams({ client_id: c.clientId, user_id: c.userId, token_url: c.tokenUrl, private_key: key });
72675
+ if (c.companyId) form.set("company_id", c.companyId);
72676
+ let status;
72677
+ let text;
72678
+ try {
72679
+ const connect = await tlsConnect(c.idpUrl);
72680
+ const res = await (0, import_undici.request)(c.idpUrl, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form.toString(), headersTimeout: 2e4, bodyTimeout: 2e4, dispatcher: connect ? new import_undici.Agent({ connect }) : void 0 });
72681
+ status = res.statusCode;
72682
+ text = (await res.body.text()).trim();
72683
+ } catch (err) {
72684
+ throw new OAuthError(`couldn't reach the assertion URL (${err instanceof Error ? err.message : "network error"})`);
72685
+ }
72686
+ if (status < 200 || status >= 300 || !text || text.startsWith("{") || text.startsWith("<")) {
72687
+ throw new OAuthError(`the assertion URL answered ${status} (${text.slice(0, 160) || "no details"}) \u2014 check the client ID, user ID and private key`);
72688
+ }
72689
+ return text;
72690
+ }
72190
72691
  async function getToken(c, opts = {}) {
72191
72692
  const key = oauthKey(c);
72192
- const kept = await load(key);
72693
+ const kept = await load2(key);
72193
72694
  if (!opts.force && fresh(kept)) return kept;
72194
72695
  if (kept?.refreshToken) {
72195
72696
  try {
72196
72697
  const next = await postToken(c, { grant_type: "refresh_token", refresh_token: kept.refreshToken });
72197
72698
  const token2 = { ...next, refreshToken: next.refreshToken ?? kept.refreshToken };
72198
- await save(key, token2);
72699
+ await save2(key, token2);
72199
72700
  return token2;
72200
72701
  } catch (err) {
72201
72702
  if (c.grant === "authorization_code") throw new OAuthError(`the refresh token stopped working (${err instanceof Error ? err.message : "error"}) \u2014 sign in in the browser again`);
72202
72703
  }
72203
72704
  }
72204
- const token = await postToken(c, grantFields(c));
72205
- await save(key, token);
72705
+ const token = await postToken(c, await grantFields(c));
72706
+ await save2(key, token);
72206
72707
  return token;
72207
72708
  }
72208
72709
  async function tokenStatus(c) {
72209
- const t = await load(oauthKey(c));
72710
+ const t = await load2(oauthKey(c));
72210
72711
  if (!t?.accessToken) return { hasToken: false, expiresAt: null, canRefresh: c.grant !== "authorization_code" };
72211
72712
  return { hasToken: true, expiresAt: t.expiresAt, scope: t.scope, canRefresh: Boolean(t.refreshToken) || c.grant !== "authorization_code", preview: `${t.accessToken.slice(0, 6)}\u2026` };
72212
72713
  }
72213
- function refreshPlan(c, t) {
72714
+ async function refreshPlan(c, t, runMs = 36e5) {
72214
72715
  if (t.expiresAt === null) return null;
72215
- const fields = c.grant === "authorization_code" ? t.refreshToken ? { grant_type: "refresh_token", refresh_token: t.refreshToken } : null : grantFields(c);
72216
- if (!fields) return null;
72217
- return { tokenUrl: c.tokenUrl, ...tokenRequest(c, fields), expiresAt: t.expiresAt };
72716
+ if (c.grant === "authorization_code") {
72717
+ return t.refreshToken ? { tokenUrl: c.tokenUrl, ...tokenRequest(c, { grant_type: "refresh_token", refresh_token: t.refreshToken }), expiresAt: t.expiresAt } : null;
72718
+ }
72719
+ if (c.grant === "saml_bearer" && c.samlSource === "idp") return null;
72720
+ const signs = c.grant === "jwt_bearer" || c.clientAuth === "private_key_jwt";
72721
+ if (!signs) {
72722
+ return { tokenUrl: c.tokenUrl, ...tokenRequest(c, await grantFields(c)), expiresAt: t.expiresAt };
72723
+ }
72724
+ const start = Math.floor(Date.now() / 1e3);
72725
+ const stepSec = 240;
72726
+ const count = Math.min(2e3, Math.ceil((runMs / 1e3 + 120) / stepSec));
72727
+ const list = [];
72728
+ let headers = {};
72729
+ for (let i = 0; i < count; i++) {
72730
+ const now = start + i * stepSec;
72731
+ const req = tokenRequest(c, await grantFields(c, now), now);
72732
+ headers = req.headers;
72733
+ list.push(req.body);
72734
+ }
72735
+ return { tokenUrl: c.tokenUrl, headers, body: list[0], expiresAt: t.expiresAt, bodies: { start, stepSec, list } };
72218
72736
  }
72219
72737
  async function startAuthorization(c) {
72220
72738
  const redirect = new URL(c.redirectUri);
@@ -72257,7 +72775,7 @@ async function startAuthorization(c) {
72257
72775
  if (!code) return finish(false, "No code came back.");
72258
72776
  const fields = { grant_type: "authorization_code", code, redirect_uri: c.redirectUri };
72259
72777
  if (c.pkce) fields.code_verifier = verifier;
72260
- postToken(c, fields).then((token) => save(oauthKey(c), token)).then(() => finish(true, "You can close this tab and go back to APITestCenter.")).catch((err) => finish(false, err instanceof Error ? `Couldn't get the token: ${err.message}` : "Couldn't get the token."));
72778
+ postToken(c, fields).then((token) => save2(oauthKey(c), token)).then(() => finish(true, "You can close this tab and go back to APITestCenter.")).catch((err) => finish(false, err instanceof Error ? `Couldn't get the token: ${err.message}` : "Couldn't get the token."));
72261
72779
  });
72262
72780
  await new Promise((resolve5, reject) => {
72263
72781
  server.once(
@@ -72319,14 +72837,15 @@ function registerOAuthRoutes(app2) {
72319
72837
  });
72320
72838
  app2.get("/oauth/authorize/:id", async (request3) => authorizationStatus(request3.params.id));
72321
72839
  }
72322
- var import_undici, store, memory, OAuthError, HINTS, fresh, pending, page, b64url2;
72840
+ var import_undici, store2, memory, OAuthError, HINTS, JWT_BEARER, SAML_BEARER, fresh, pending, page, b64url2;
72323
72841
  var init_oauth = __esm({
72324
72842
  "../../apps/engine/src/oauth.ts"() {
72325
72843
  "use strict";
72326
72844
  init_src();
72327
72845
  import_undici = __toESM(require_undici(), 1);
72328
72846
  init_certificates();
72329
- store = null;
72847
+ init_jwt();
72848
+ store2 = null;
72330
72849
  memory = /* @__PURE__ */ new Map();
72331
72850
  OAuthError = class extends Error {
72332
72851
  };
@@ -72337,6 +72856,8 @@ var init_oauth = __esm({
72337
72856
  invalid_scope: "the scope isn't allowed for this client",
72338
72857
  unsupported_grant_type: "the server doesn't offer this grant type"
72339
72858
  };
72859
+ JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer";
72860
+ SAML_BEARER = "urn:ietf:params:oauth:grant-type:saml2-bearer";
72340
72861
  fresh = (t, marginMs = 6e4) => Boolean(t && (t.expiresAt === null || t.expiresAt - marginMs > Date.now()));
72341
72862
  pending = /* @__PURE__ */ new Map();
72342
72863
  page = (title, text) => `<!doctype html><meta charset="utf-8"><title>${title}</title><body style="font-family:system-ui;background:#0b0f14;color:#e6edf3;display:grid;place-items:center;height:100vh;margin:0"><div style="max-width:28rem;text-align:center"><h1 style="font-size:1.3rem">${title}</h1><p style="color:#9aa7b4">${text}</p></div>`;
@@ -72518,6 +73039,30 @@ var init_aws_sign = __esm({
72518
73039
  }
72519
73040
  });
72520
73041
 
73042
+ // ../../apps/engine/src/oauth1-sign.ts
73043
+ import { createHmac as createHmac4, randomBytes as randomBytes3 } from "node:crypto";
73044
+ function signOAuth1(c, req, nonce = randomBytes3(16).toString("hex"), timestamp = Math.floor(Date.now() / 1e3)) {
73045
+ const oauth = oauth1Params(c, nonce, timestamp);
73046
+ const query = /\?([^#]*)/.exec(req.url)?.[1] ?? "";
73047
+ const params = [...oauth, ...formPairs(query)];
73048
+ if (req.body && /^application\/x-www-form-urlencoded/i.test(req.contentType ?? "")) params.push(...formPairs(req.body));
73049
+ const baseString = oauth1BaseString(req.method, req.url, params);
73050
+ const key = oauth1SigningKey(c);
73051
+ const signature = c.signatureMethod === "PLAINTEXT" ? key : createHmac4(c.signatureMethod === "HMAC-SHA1" ? "sha1" : "sha256", key).update(baseString).digest("base64");
73052
+ const signed = [...oauth, ["oauth_signature", signature]];
73053
+ if (c.addTo === "query") {
73054
+ const extra2 = signed.map(([k, v]) => `${pctEncode(k)}=${pctEncode(v)}`).join("&");
73055
+ return { url: `${req.url}${req.url.includes("?") ? "&" : "?"}${extra2}`, signature, baseString };
73056
+ }
73057
+ return { url: req.url, authorization: oauth1Header(c, signed), signature, baseString };
73058
+ }
73059
+ var init_oauth1_sign = __esm({
73060
+ "../../apps/engine/src/oauth1-sign.ts"() {
73061
+ "use strict";
73062
+ init_src();
73063
+ }
73064
+ });
73065
+
72521
73066
  // ../../apps/engine/src/probe.ts
72522
73067
  function redactHeaders(headers, extraNames = []) {
72523
73068
  const extra2 = new Set(extraNames.map((n) => n.toLowerCase()));
@@ -72714,6 +73259,16 @@ async function sendRaw(lab, extra2 = {}) {
72714
73259
  if (encoded.contentType && !Object.keys(headers).some((k) => k.toLowerCase() === "content-type")) {
72715
73260
  headers["content-type"] = encoded.contentType;
72716
73261
  }
73262
+ let target = url2;
73263
+ if (lab.auth.mode === "oauth1") {
73264
+ const { mode: _mode, ...creds } = lab.auth;
73265
+ const contentType = Object.entries(headers).find(([k]) => k.toLowerCase() === "content-type")?.[1];
73266
+ const signed = signOAuth1(creds, { method: lab.request.method, url: url2.toString(), contentType, body: encoded.body });
73267
+ if (signed.authorization) {
73268
+ headers.authorization = signed.authorization;
73269
+ secretNames.push("authorization");
73270
+ } else target = new URL(signed.url);
73271
+ }
72717
73272
  if (lab.auth.mode === "aws_sigv4") {
72718
73273
  const { mode: _mode, ...creds } = lab.auth;
72719
73274
  Object.assign(headers, signAws(creds, { method: lab.request.method, url: url2.toString(), headers, body: encoded.body }));
@@ -72721,20 +73276,39 @@ async function sendRaw(lab, extra2 = {}) {
72721
73276
  }
72722
73277
  const options = lab.options;
72723
73278
  const timeout = durationToMs(options.timeout);
72724
- const dispatcher = extra2.dispatcher ?? new import_undici2.Agent({
72725
- // A client certificate (mutual TLS) for this host, if one is set.
72726
- connect: await tlsConnect(url2, options.tlsSkipVerify)
72727
- }).compose(import_undici2.interceptors.redirect({ maxRedirections: options.maxRedirects }));
73279
+ const useJar = options.cookies !== false && !extra2.dispatcher;
73280
+ const ownCookie = Object.keys(headers).some((k) => k.toLowerCase() === "cookie");
73281
+ const agent = extra2.dispatcher ?? new import_undici2.Agent({ connect: await tlsConnect(url2, options.tlsSkipVerify) });
73282
+ const dispatcher = extra2.dispatcher ?? (useJar ? agent : agent.compose(import_undici2.interceptors.redirect({ maxRedirections: options.maxRedirects })));
72728
73283
  const started = performance.now();
72729
73284
  try {
72730
- const response = await (0, import_undici2.request)(url2, {
72731
- method: lab.request.method,
72732
- headers,
72733
- body: encoded.body,
72734
- headersTimeout: timeout,
72735
- bodyTimeout: timeout,
72736
- dispatcher
72737
- });
73285
+ let current = target;
73286
+ let method = lab.request.method;
73287
+ let body = encoded.body;
73288
+ let response;
73289
+ for (let hops = 0; ; hops++) {
73290
+ const sendHeaders = { ...headers };
73291
+ if (useJar && !ownCookie) {
73292
+ const jar2 = await cookieHeader(current);
73293
+ if (jar2) sendHeaders.cookie = jar2;
73294
+ }
73295
+ response = await (0, import_undici2.request)(current, { method, headers: sendHeaders, body, headersTimeout: timeout, bodyTimeout: timeout, dispatcher });
73296
+ if (!useJar) break;
73297
+ await storeCookies(current, response.headers["set-cookie"]);
73298
+ const location = response.headers.location;
73299
+ if (![301, 302, 303, 307, 308].includes(response.statusCode) || typeof location !== "string" || hops >= options.maxRedirects) break;
73300
+ await response.body.dump();
73301
+ const next = new URL(location, current);
73302
+ if (next.origin !== current.origin) {
73303
+ for (const k of Object.keys(headers)) if (k.toLowerCase() === "authorization") delete headers[k];
73304
+ }
73305
+ if (response.statusCode === 303 || (response.statusCode === 301 || response.statusCode === 302) && method === "POST") {
73306
+ method = "GET";
73307
+ body = void 0;
73308
+ for (const k of Object.keys(headers)) if (k.toLowerCase() === "content-type") delete headers[k];
73309
+ }
73310
+ current = next;
73311
+ }
72738
73312
  const raw = await response.body.text();
72739
73313
  const durationMs = Math.round((performance.now() - started) * 10) / 10;
72740
73314
  const truncated = raw.length > BODY_LIMIT;
@@ -72789,8 +73363,10 @@ var init_probe = __esm({
72789
73363
  import_undici2 = __toESM(require_undici(), 1);
72790
73364
  init_challenge_auth();
72791
73365
  init_aws_sign();
73366
+ init_oauth1_sign();
72792
73367
  init_jwt();
72793
73368
  init_certificates();
73369
+ init_cookies();
72794
73370
  init_oauth();
72795
73371
  BODY_LIMIT = 64 * 1024;
72796
73372
  }
@@ -73138,12 +73714,12 @@ function dataDefinition(lab) {
73138
73714
  };
73139
73715
  }
73140
73716
  function loadDefinition(run2) {
73141
- const load2 = run2.lab.load;
73142
- const rate = isArrivalRate(load2.executor);
73717
+ const load3 = run2.lab.load;
73718
+ const rate = isArrivalRate(load3.executor);
73143
73719
  return {
73144
- executor: load2.executor,
73145
- rate: rate ? load2.rate ?? Math.max(0, ...load2.stages.map((s) => s.target)) : void 0,
73146
- stages: load2.stages.length > 0 ? load2.stages : [{ duration: load2.duration, target: rate ? load2.rate ?? 0 : load2.vus }]
73720
+ executor: load3.executor,
73721
+ rate: rate ? load3.rate ?? Math.max(0, ...load3.stages.map((s) => s.target)) : void 0,
73722
+ stages: load3.stages.length > 0 ? load3.stages : [{ duration: load3.duration, target: rate ? load3.rate ?? 0 : load3.vus }]
73147
73723
  };
73148
73724
  }
73149
73725
  function liveSummaryParts(run2) {
@@ -73362,7 +73938,7 @@ async function startRun(lab, repoRoot2) {
73362
73938
  } catch (err) {
73363
73939
  throw Object.assign(new Error(`OAuth 2.0: ${err instanceof Error ? err.message : String(err)}`), { statusCode: 422 });
73364
73940
  }
73365
- refresh = refreshPlan(config2, token);
73941
+ refresh = await refreshPlan(config2, token, loadDurationHintMs(filled.load));
73366
73942
  filled = { ...filled, auth: { mode: "bearer", token: token.accessToken } };
73367
73943
  }
73368
73944
  let tlsAuth;
@@ -73382,7 +73958,19 @@ async function startRun(lab, repoRoot2) {
73382
73958
  }
73383
73959
  filled = { ...filled, auth: { mode: "none" } };
73384
73960
  }
73961
+ const bundle = await caBundle();
73962
+ if (bundle && process.platform !== "linux" && !filled.options.tlsSkipVerify && await needsTrustedCa(filled.request.url)) {
73963
+ filled = { ...filled, options: { ...filled.options, tlsSkipVerify: true } };
73964
+ }
73385
73965
  const generated = generateK6Script(filled, { oauthRefresh: refresh, tlsAuth, jwt: jwt2 });
73966
+ if (filled.options.cookies !== false && !filled.request.headers.some((h) => h.key.toLowerCase() === "cookie")) {
73967
+ const jar2 = await cookieHeader(filled.request.url);
73968
+ if (jar2) generated.env.ATC_COOKIE = jar2;
73969
+ }
73970
+ if (bundle && process.platform === "linux") {
73971
+ await writeFile3(join5(dir2, "ca-bundle.pem"), bundle, "utf8");
73972
+ generated.env.SSL_CERT_FILE = join5(dir2, "ca-bundle.pem");
73973
+ }
73386
73974
  await writeFile3(scriptPath, generated.script, "utf8");
73387
73975
  for (const [name, text] of Object.entries(generated.files)) await writeFile3(join5(dir2, name), text, "utf8");
73388
73976
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -73529,6 +74117,7 @@ var init_load_run = __esm({
73529
74117
  init_k6_script();
73530
74118
  init_src();
73531
74119
  init_certificates();
74120
+ init_cookies();
73532
74121
  init_jwt();
73533
74122
  init_oauth();
73534
74123
  init_probe();
@@ -73542,7 +74131,7 @@ var init_load_run = __esm({
73542
74131
  });
73543
74132
 
73544
74133
  // ../../apps/engine/src/secret-store.ts
73545
- import { createCipheriv, createDecipheriv, randomBytes as randomBytes3 } from "node:crypto";
74134
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes4 } from "node:crypto";
73546
74135
  import { readFile as readFile4, rename as rename3, writeFile as writeFile4 } from "node:fs/promises";
73547
74136
  import { join as join6 } from "node:path";
73548
74137
  var SecretStore;
@@ -73579,7 +74168,7 @@ var init_secret_store = __esm({
73579
74168
  }
73580
74169
  async persist() {
73581
74170
  if (!this.key) return;
73582
- const iv = randomBytes3(12);
74171
+ const iv = randomBytes4(12);
73583
74172
  const cipher = createCipheriv("aes-256-gcm", this.key, iv);
73584
74173
  const body = Buffer.concat([cipher.update(JSON.stringify(Object.fromEntries(this.values)), "utf8"), cipher.final()]);
73585
74174
  const tmp = `${this.file}.${process.pid}.tmp`;
@@ -78624,14 +79213,14 @@ var require_wrap_thenable = __commonJS({
78624
79213
  var { setErrorStatusCode } = require_error_status();
78625
79214
  var diagnostics = __require("node:diagnostics_channel");
78626
79215
  var channels = diagnostics.tracingChannel("fastify.request.handler");
78627
- function wrapThenable(thenable, reply, store2) {
78628
- if (store2) store2.async = true;
79216
+ function wrapThenable(thenable, reply, store3) {
79217
+ if (store3) store3.async = true;
78629
79218
  thenable.then(function(payload) {
78630
79219
  if (reply[kReplyHijacked] === true) {
78631
79220
  return;
78632
79221
  }
78633
- if (store2) {
78634
- channels.asyncStart.publish(store2);
79222
+ if (store3) {
79223
+ channels.asyncStart.publish(store3);
78635
79224
  }
78636
79225
  try {
78637
79226
  if (payload !== void 0 || //
@@ -78645,16 +79234,16 @@ var require_wrap_thenable = __commonJS({
78645
79234
  }
78646
79235
  }
78647
79236
  } finally {
78648
- if (store2) {
78649
- channels.asyncEnd.publish(store2);
79237
+ if (store3) {
79238
+ channels.asyncEnd.publish(store3);
78650
79239
  }
78651
79240
  }
78652
79241
  }, function(err) {
78653
- if (store2) {
78654
- store2.error = err;
79242
+ if (store3) {
79243
+ store3.error = err;
78655
79244
  setErrorStatusCode(reply, err);
78656
- channels.error.publish(store2);
78657
- channels.asyncStart.publish(store2);
79245
+ channels.error.publish(store3);
79246
+ channels.asyncStart.publish(store3);
78658
79247
  }
78659
79248
  try {
78660
79249
  if (reply.sent === true) {
@@ -78666,8 +79255,8 @@ var require_wrap_thenable = __commonJS({
78666
79255
  } catch (err2) {
78667
79256
  reply.send(err2);
78668
79257
  } finally {
78669
- if (store2) {
78670
- channels.asyncEnd.publish(store2);
79258
+ if (store3) {
79259
+ channels.asyncEnd.publish(store3);
78671
79260
  }
78672
79261
  }
78673
79262
  });
@@ -78994,10 +79583,10 @@ var require_content_type = __commonJS({
78994
79583
  this.#subtype = subtype;
78995
79584
  this.#valid = true;
78996
79585
  this.#empty = false;
78997
- let matches = keyValuePairsReg.exec(paramsList);
78998
- while (matches) {
78999
- const key = matches[1].toLowerCase();
79000
- let value = matches[2];
79586
+ let matches2 = keyValuePairsReg.exec(paramsList);
79587
+ while (matches2) {
79588
+ const key = matches2[1].toLowerCase();
79589
+ let value = matches2[2];
79001
79590
  if (value.charCodeAt(0) === 34) {
79002
79591
  value = value.slice(1, -1);
79003
79592
  if (value.indexOf("\\") !== -1) {
@@ -79005,7 +79594,7 @@ var require_content_type = __commonJS({
79005
79594
  }
79006
79595
  }
79007
79596
  this.#parameters.set(key, value);
79008
- matches = keyValuePairsReg.exec(paramsList);
79597
+ matches2 = keyValuePairsReg.exec(paramsList);
79009
79598
  }
79010
79599
  }
79011
79600
  get [Symbol.toStringTag]() {
@@ -79183,7 +79772,7 @@ var require_handle_request = __commonJS({
79183
79772
  if (!channels.hasSubscribers || context[kFourOhFourContext] === null) {
79184
79773
  preHandlerCallbackInner(err, request3, reply);
79185
79774
  } else {
79186
- const store2 = {
79775
+ const store3 = {
79187
79776
  request: request3,
79188
79777
  reply,
79189
79778
  async: false,
@@ -79192,19 +79781,19 @@ var require_handle_request = __commonJS({
79192
79781
  method: context.config.method
79193
79782
  }
79194
79783
  };
79195
- reply[kDiagnosticsStore] = store2;
79196
- channels.start.runStores(store2, preHandlerCallbackInner, void 0, err, request3, reply, store2);
79784
+ reply[kDiagnosticsStore] = store3;
79785
+ channels.start.runStores(store3, preHandlerCallbackInner, void 0, err, request3, reply, store3);
79197
79786
  }
79198
79787
  }
79199
- function preHandlerCallbackInner(err, request3, reply, store2) {
79788
+ function preHandlerCallbackInner(err, request3, reply, store3) {
79200
79789
  const context = request3[kRouteContext];
79201
79790
  try {
79202
79791
  if (err != null) {
79203
79792
  reply[kReplyIsError] = true;
79204
- if (store2) {
79205
- store2.error = err;
79793
+ if (store3) {
79794
+ store3.error = err;
79206
79795
  setErrorStatusCode(reply, err);
79207
- channels.error.publish(store2);
79796
+ channels.error.publish(store3);
79208
79797
  }
79209
79798
  reply.send(err);
79210
79799
  return;
@@ -79213,10 +79802,10 @@ var require_handle_request = __commonJS({
79213
79802
  try {
79214
79803
  result = context.handler(request3, reply);
79215
79804
  } catch (err2) {
79216
- if (store2) {
79217
- store2.error = err2;
79805
+ if (store3) {
79806
+ store3.error = err2;
79218
79807
  setErrorStatusCode(reply, err2);
79219
- channels.error.publish(store2);
79808
+ channels.error.publish(store3);
79220
79809
  }
79221
79810
  reply[kReplyIsError] = true;
79222
79811
  reply.send(err2);
@@ -79224,13 +79813,13 @@ var require_handle_request = __commonJS({
79224
79813
  }
79225
79814
  if (result !== void 0) {
79226
79815
  if (result !== null && typeof result.then === "function") {
79227
- wrapThenable(result, reply, store2);
79816
+ wrapThenable(result, reply, store3);
79228
79817
  } else {
79229
79818
  reply.send(result);
79230
79819
  }
79231
79820
  }
79232
79821
  } finally {
79233
- if (store2) channels.end.publish(store2);
79822
+ if (store3) channels.end.publish(store3);
79234
79823
  }
79235
79824
  }
79236
79825
  module.exports = handleRequest;
@@ -83627,8 +84216,8 @@ var require_tools = __commonJS({
83627
84216
  if (asJsonChan.hasSubscribers === false) {
83628
84217
  return _asJson.call(this, obj, msg, num3, time3);
83629
84218
  }
83630
- const store2 = { instance: this, arguments };
83631
- return asJsonChan.traceSync(_asJson, store2, this, obj, msg, num3, time3);
84219
+ const store3 = { instance: this, arguments };
84220
+ return asJsonChan.traceSync(_asJson, store3, this, obj, msg, num3, time3);
83632
84221
  }
83633
84222
  function _asJson(obj, msg, num3, time3) {
83634
84223
  const stringify2 = this[stringifySym];
@@ -85896,8 +86485,8 @@ var require_error_handler = __commonJS({
85896
86485
  const result = func(error51, reply.request, reply);
85897
86486
  if (result !== void 0) {
85898
86487
  if (result !== null && typeof result.then === "function") {
85899
- const store2 = reply[kDiagnosticsStore] || null;
85900
- wrapThenable(result, reply, store2);
86488
+ const store3 = reply[kDiagnosticsStore] || null;
86489
+ wrapThenable(result, reply, store3);
85901
86490
  } else {
85902
86491
  reply.send(result);
85903
86492
  }
@@ -88216,11 +88805,11 @@ var require_request3 = __commonJS({
88216
88805
  port: {
88217
88806
  get() {
88218
88807
  const portReg = /(?<port>:\d+)$/;
88219
- const matches = portReg.exec(this.host);
88220
- if (matches === null || matches[1] === void 0) {
88808
+ const matches2 = portReg.exec(this.host);
88809
+ if (matches2 === null || matches2[1] === void 0) {
88221
88810
  return null;
88222
88811
  }
88223
- return parseInt(matches.groups.port.slice(1), 10);
88812
+ return parseInt(matches2.groups.port.slice(1), 10);
88224
88813
  }
88225
88814
  },
88226
88815
  protocol: {
@@ -91627,11 +92216,11 @@ var require_validate = __commonJS({
91627
92216
  jsonPointer = $data;
91628
92217
  data = names_1.default.rootData;
91629
92218
  } else {
91630
- const matches = RELATIVE_JSON_POINTER.exec($data);
91631
- if (!matches)
92219
+ const matches2 = RELATIVE_JSON_POINTER.exec($data);
92220
+ if (!matches2)
91632
92221
  throw new Error(`Invalid JSON-pointer: ${$data}`);
91633
- const up = +matches[1];
91634
- jsonPointer = matches[2];
92222
+ const up = +matches2[1];
92223
+ jsonPointer = matches2[2];
91635
92224
  if (jsonPointer === "#") {
91636
92225
  if (up >= dataLevel)
91637
92226
  throw new Error(errorMsg("property/index", up));
@@ -92328,11 +92917,11 @@ var require_schemes = __commonJS({
92328
92917
  urnComponent.error = "URN can not be parsed";
92329
92918
  return urnComponent;
92330
92919
  }
92331
- const matches = urnComponent.path.match(URN_REG);
92332
- if (matches) {
92920
+ const matches2 = urnComponent.path.match(URN_REG);
92921
+ if (matches2) {
92333
92922
  const scheme = options.scheme || urnComponent.scheme || "urn";
92334
- urnComponent.nid = matches[1].toLowerCase();
92335
- urnComponent.nss = matches[2];
92923
+ urnComponent.nid = matches2[1].toLowerCase();
92924
+ urnComponent.nss = matches2[2];
92336
92925
  const urnScheme = `${scheme}:${options.nid || urnComponent.nid}`;
92337
92926
  const schemeHandler = getSchemeHandler(urnScheme);
92338
92927
  urnComponent.path = void 0;
@@ -92609,8 +93198,8 @@ var require_fast_uri = __commonJS({
92609
93198
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
92610
93199
  var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
92611
93200
  var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
92612
- function getParseError(parsed, matches) {
92613
- if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
93201
+ function getParseError(parsed, matches2) {
93202
+ if (matches2[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
92614
93203
  return 'URI path must start with "/" when authority is present.';
92615
93204
  }
92616
93205
  if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
@@ -92657,19 +93246,19 @@ var require_fast_uri = __commonJS({
92657
93246
  }
92658
93247
  }
92659
93248
  }
92660
- const matches = uri.match(URI_PARSE);
92661
- if (matches) {
92662
- parsed.scheme = matches[1];
92663
- parsed.userinfo = matches[3];
92664
- parsed.host = matches[4];
92665
- parsed.port = parseInt(matches[5], 10);
92666
- parsed.path = matches[6] || "";
92667
- parsed.query = matches[7];
92668
- parsed.fragment = matches[8];
93249
+ const matches2 = uri.match(URI_PARSE);
93250
+ if (matches2) {
93251
+ parsed.scheme = matches2[1];
93252
+ parsed.userinfo = matches2[3];
93253
+ parsed.host = matches2[4];
93254
+ parsed.port = parseInt(matches2[5], 10);
93255
+ parsed.path = matches2[6] || "";
93256
+ parsed.query = matches2[7];
93257
+ parsed.fragment = matches2[8];
92669
93258
  if (isNaN(parsed.port)) {
92670
- parsed.port = matches[5];
93259
+ parsed.port = matches2[5];
92671
93260
  }
92672
- const parseError = getParseError(parsed, matches);
93261
+ const parseError = getParseError(parsed, matches2);
92673
93262
  if (parseError !== void 0) {
92674
93263
  parsed.error = parsed.error || parseError;
92675
93264
  malformedAuthorityOrPort = true;
@@ -95547,24 +96136,24 @@ var require_timestamp2 = __commonJS({
95547
96136
  }
95548
96137
  exports.default = validTimestamp;
95549
96138
  function validDate(str2) {
95550
- const matches = DATE.exec(str2);
95551
- if (!matches)
96139
+ const matches2 = DATE.exec(str2);
96140
+ if (!matches2)
95552
96141
  return false;
95553
- const y = +matches[1];
95554
- const m = +matches[2];
95555
- const d = +matches[3];
96142
+ const y = +matches2[1];
96143
+ const m = +matches2[2];
96144
+ const d = +matches2[3];
95556
96145
  return m >= 1 && m <= 12 && d >= 1 && (d <= DAYS[m] || // leap year: https://tools.ietf.org/html/rfc3339#appendix-C
95557
96146
  m === 2 && d === 29 && (y % 100 === 0 ? y % 400 === 0 : y % 4 === 0));
95558
96147
  }
95559
96148
  function validTime(str2) {
95560
- const matches = TIME.exec(str2);
95561
- if (!matches)
96149
+ const matches2 = TIME.exec(str2);
96150
+ if (!matches2)
95562
96151
  return false;
95563
- const hr = +matches[1];
95564
- const min = +matches[2];
95565
- const sec = +matches[3];
95566
- const tzH = +(matches[4] || 0);
95567
- const tzM = +(matches[5] || 0);
96152
+ const hr = +matches2[1];
96153
+ const min = +matches2[2];
96154
+ const sec = +matches2[3];
96155
+ const tzH = +(matches2[4] || 0);
96156
+ const tzM = +(matches2[5] || 0);
95568
96157
  return hr <= 23 && min <= 59 && sec <= 59 || // leap second
95569
96158
  hr - tzH === 23 && min - tzM === 59 && sec === 60;
95570
96159
  }
@@ -96518,19 +97107,19 @@ var require_parseJson = __commonJS({
96518
97107
  function parseJson(s, pos) {
96519
97108
  let endPos;
96520
97109
  parseJson.message = void 0;
96521
- let matches;
97110
+ let matches2;
96522
97111
  if (pos)
96523
97112
  s = s.slice(pos);
96524
97113
  try {
96525
97114
  parseJson.position = pos + s.length;
96526
97115
  return JSON.parse(s);
96527
97116
  } catch (e) {
96528
- matches = rxParseJson.exec(e.message);
96529
- if (!matches) {
97117
+ matches2 = rxParseJson.exec(e.message);
97118
+ if (!matches2) {
96530
97119
  parseJson.message = "unexpected end";
96531
97120
  return void 0;
96532
97121
  }
96533
- endPos = +matches[1];
97122
+ endPos = +matches2[1];
96534
97123
  const c = s[endPos];
96535
97124
  s = s.slice(0, endPos);
96536
97125
  parseJson.position = pos + endPos;
@@ -97612,11 +98201,11 @@ var require_schemes2 = __commonJS({
97612
98201
  urnComponent.error = "URN can not be parsed";
97613
98202
  return urnComponent;
97614
98203
  }
97615
- const matches = urnComponent.path.match(URN_REG);
97616
- if (matches) {
98204
+ const matches2 = urnComponent.path.match(URN_REG);
98205
+ if (matches2) {
97617
98206
  const scheme = options.scheme || urnComponent.scheme || "urn";
97618
- urnComponent.nid = matches[1].toLowerCase();
97619
- urnComponent.nss = matches[2];
98207
+ urnComponent.nid = matches2[1].toLowerCase();
98208
+ urnComponent.nss = matches2[2];
97620
98209
  const urnScheme = `${scheme}:${options.nid || urnComponent.nid}`;
97621
98210
  const schemeHandler = getSchemeHandler(urnScheme);
97622
98211
  urnComponent.path = void 0;
@@ -97893,8 +98482,8 @@ var require_fast_uri2 = __commonJS({
97893
98482
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
97894
98483
  var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
97895
98484
  var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
97896
- function getParseError(parsed, matches) {
97897
- if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
98485
+ function getParseError(parsed, matches2) {
98486
+ if (matches2[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
97898
98487
  return 'URI path must start with "/" when authority is present.';
97899
98488
  }
97900
98489
  if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
@@ -97941,19 +98530,19 @@ var require_fast_uri2 = __commonJS({
97941
98530
  }
97942
98531
  }
97943
98532
  }
97944
- const matches = uri.match(URI_PARSE);
97945
- if (matches) {
97946
- parsed.scheme = matches[1] === void 0 ? void 0 : matches[1].toLowerCase();
97947
- parsed.userinfo = matches[3];
97948
- parsed.host = matches[4];
97949
- parsed.port = parseInt(matches[5], 10);
97950
- parsed.path = matches[6] || "";
97951
- parsed.query = matches[7];
97952
- parsed.fragment = matches[8];
98533
+ const matches2 = uri.match(URI_PARSE);
98534
+ if (matches2) {
98535
+ parsed.scheme = matches2[1] === void 0 ? void 0 : matches2[1].toLowerCase();
98536
+ parsed.userinfo = matches2[3];
98537
+ parsed.host = matches2[4];
98538
+ parsed.port = parseInt(matches2[5], 10);
98539
+ parsed.path = matches2[6] || "";
98540
+ parsed.query = matches2[7];
98541
+ parsed.fragment = matches2[8];
97953
98542
  if (isNaN(parsed.port)) {
97954
- parsed.port = matches[5];
98543
+ parsed.port = matches2[5];
97955
98544
  }
97956
- const parseError = getParseError(parsed, matches);
98545
+ const parseError = getParseError(parsed, matches2);
97957
98546
  if (parseError !== void 0) {
97958
98547
  parsed.error = parsed.error || parseError;
97959
98548
  malformedAuthorityOrPort = true;
@@ -98150,12 +98739,12 @@ var require_formats = __commonJS({
98150
98739
  var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
98151
98740
  var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
98152
98741
  function date5(str2) {
98153
- const matches = DATE.exec(str2);
98154
- if (!matches)
98742
+ const matches2 = DATE.exec(str2);
98743
+ if (!matches2)
98155
98744
  return false;
98156
- const year = +matches[1];
98157
- const month = +matches[2];
98158
- const day = +matches[3];
98745
+ const year = +matches2[1];
98746
+ const month = +matches2[2];
98747
+ const day = +matches2[3];
98159
98748
  return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]);
98160
98749
  }
98161
98750
  function compareDate(d1, d2) {
@@ -98170,16 +98759,16 @@ var require_formats = __commonJS({
98170
98759
  var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
98171
98760
  function getTime(strictTimeZone) {
98172
98761
  return function time3(str2) {
98173
- const matches = TIME.exec(str2);
98174
- if (!matches)
98762
+ const matches2 = TIME.exec(str2);
98763
+ if (!matches2)
98175
98764
  return false;
98176
- const hr = +matches[1];
98177
- const min = +matches[2];
98178
- const sec = +matches[3];
98179
- const tz = matches[4];
98180
- const tzSign = matches[5] === "-" ? -1 : 1;
98181
- const tzH = +(matches[6] || 0);
98182
- const tzM = +(matches[7] || 0);
98765
+ const hr = +matches2[1];
98766
+ const min = +matches2[2];
98767
+ const sec = +matches2[3];
98768
+ const tz = matches2[4];
98769
+ const tzSign = matches2[5] === "-" ? -1 : 1;
98770
+ const tzH = +(matches2[6] || 0);
98771
+ const tzM = +(matches2[7] || 0);
98183
98772
  if (tzH > 23 || tzM > 59 || strictTimeZone && !tz)
98184
98773
  return false;
98185
98774
  if (hr <= 23 && min <= 59 && sec < 60)
@@ -105663,8 +106252,8 @@ var require_http_method = __commonJS({
105663
106252
  get: (type) => {
105664
106253
  return handlers.get(type) || null;
105665
106254
  },
105666
- set: (type, store2) => {
105667
- handlers.set(type, store2);
106255
+ set: (type, store3) => {
106256
+ handlers.set(type, store3);
105668
106257
  }
105669
106258
  };
105670
106259
  },
@@ -105903,14 +106492,14 @@ var require_handler_storage = __commonJS({
105903
106492
  // Builds a store object that maps from constraint values to a bitmap of handler indexes which pass the constraint for a value
105904
106493
  // So for a host constraint, this might look like { "fastify.io": 0b0010, "google.ca": 0b0101 }, meaning the 3rd handler is constrainted to fastify.io, and the 2nd and 4th handlers are constrained to google.ca.
105905
106494
  // The store's implementation comes from the strategies provided to the Router.
105906
- _buildConstraintStore(store2, constraint) {
106495
+ _buildConstraintStore(store3, constraint) {
105907
106496
  for (let i = 0; i < this.handlers.length; i++) {
105908
106497
  const handler = this.handlers[i];
105909
106498
  const constraintValue = handler.constraints[constraint];
105910
106499
  if (constraintValue !== void 0) {
105911
- let indexes = store2.get(constraintValue) || 0;
106500
+ let indexes = store3.get(constraintValue) || 0;
105912
106501
  indexes |= 1 << i;
105913
- store2.set(constraintValue, indexes);
106502
+ store3.set(constraintValue, indexes);
105914
106503
  }
105915
106504
  }
105916
106505
  }
@@ -105935,9 +106524,9 @@ var require_handler_storage = __commonJS({
105935
106524
  _compileGetHandlerMatchingConstraints(constrainer) {
105936
106525
  this.constrainedHandlerStores = {};
105937
106526
  for (const constraint of this.constraints) {
105938
- const store2 = constrainer.newStoreForConstraint(constraint);
105939
- this.constrainedHandlerStores[constraint] = store2;
105940
- this._buildConstraintStore(store2, constraint);
106527
+ const store3 = constrainer.newStoreForConstraint(constraint);
106528
+ this.constrainedHandlerStores[constraint] = store3;
106529
+ this._buildConstraintStore(store3, constraint);
105941
106530
  }
105942
106531
  const lines = [];
105943
106532
  lines.push(`
@@ -106200,7 +106789,7 @@ var require_accept_version = __commonJS({
106200
106789
  this.maxMinors = {};
106201
106790
  this.maxPatches = {};
106202
106791
  }
106203
- SemVerStore.prototype.set = function(version2, store2) {
106792
+ SemVerStore.prototype.set = function(version2, store3) {
106204
106793
  if (typeof version2 !== "string") {
106205
106794
  throw new TypeError("Version should be a string");
106206
106795
  }
@@ -106213,21 +106802,21 @@ var require_accept_version = __commonJS({
106213
106802
  patch = Number(patch) || 0;
106214
106803
  if (major >= this.maxMajor) {
106215
106804
  this.maxMajor = major;
106216
- this.store.set("x", store2);
106217
- this.store.set("*", store2);
106218
- this.store.set("x.x", store2);
106219
- this.store.set("x.x.x", store2);
106805
+ this.store.set("x", store3);
106806
+ this.store.set("*", store3);
106807
+ this.store.set("x.x", store3);
106808
+ this.store.set("x.x.x", store3);
106220
106809
  }
106221
106810
  if (minor >= (this.maxMinors[major] || 0)) {
106222
106811
  this.maxMinors[major] = minor;
106223
- this.store.set(`${major}.x`, store2);
106224
- this.store.set(`${major}.x.x`, store2);
106812
+ this.store.set(`${major}.x`, store3);
106813
+ this.store.set(`${major}.x.x`, store3);
106225
106814
  }
106226
106815
  if (patch >= (this.maxPatches[`${major}.${minor}`] || 0)) {
106227
106816
  this.maxPatches[`${major}.${minor}`] = patch;
106228
- this.store.set(`${major}.${minor}.x`, store2);
106817
+ this.store.set(`${major}.${minor}.x`, store3);
106229
106818
  }
106230
- this.store.set(`${major}.${minor}.${patch}`, store2);
106819
+ this.store.set(`${major}.${minor}.${patch}`, store3);
106231
106820
  return this;
106232
106821
  };
106233
106822
  SemVerStore.prototype.get = function(version2) {
@@ -106695,10 +107284,10 @@ var require_find_my_way = __commonJS({
106695
107284
  this.trees = /* @__PURE__ */ Object.create(null);
106696
107285
  this._treeGET = null;
106697
107286
  }
106698
- Router.prototype.on = function on(method, path, opts, handler, store2) {
107287
+ Router.prototype.on = function on(method, path, opts, handler, store3) {
106699
107288
  if (typeof opts === "function") {
106700
107289
  if (handler !== void 0) {
106701
- store2 = handler;
107290
+ store3 = handler;
106702
107291
  }
106703
107292
  handler = opts;
106704
107293
  opts = {};
@@ -106712,8 +107301,8 @@ var require_find_my_way = __commonJS({
106712
107301
  assert2(path.length === optionalParamMatch.index + optionalParamMatch[0].length, "Optional Parameter needs to be the last parameter of the path");
106713
107302
  const pathFull = path.replace(OPTIONAL_PARAM_REGEXP, "$1$2");
106714
107303
  const pathOptional = path.replace(OPTIONAL_PARAM_REGEXP, "$2") || "/";
106715
- this.on(method, pathFull, opts, handler, store2);
106716
- this.on(method, pathOptional, opts, handler, store2);
107304
+ this.on(method, pathFull, opts, handler, store3);
107305
+ this.on(method, pathOptional, opts, handler, store3);
106717
107306
  return;
106718
107307
  }
106719
107308
  const route = path;
@@ -106727,10 +107316,10 @@ var require_find_my_way = __commonJS({
106727
107316
  for (const method2 of methods) {
106728
107317
  assert2(typeof method2 === "string", "Method should be a string");
106729
107318
  assert2(httpMethods.includes(method2), `Method '${method2}' is not an http method.`);
106730
- this._on(method2, path, opts, handler, store2, route);
107319
+ this._on(method2, path, opts, handler, store3, route);
106731
107320
  }
106732
107321
  };
106733
- Router.prototype._on = function _on(method, path, opts, handler, store2) {
107322
+ Router.prototype._on = function _on(method, path, opts, handler, store3) {
106734
107323
  let constraints = {};
106735
107324
  if (opts.constraints !== void 0) {
106736
107325
  assert2(typeof opts.constraints === "object" && opts.constraints !== null, "Constraints should be an object");
@@ -106851,7 +107440,7 @@ var require_find_my_way = __commonJS({
106851
107440
  throw new Error(`Method '${method}' already declared for route '${pattern}' with constraints '${JSON.stringify(constraints)}'`);
106852
107441
  }
106853
107442
  }
106854
- const route = { method, path, pattern, params, opts, handler, store: store2 };
107443
+ const route = { method, path, pattern, params, opts, handler, store: store3 };
106855
107444
  this.routes.push(route);
106856
107445
  currentNode.addRoute(route, this.constrainer);
106857
107446
  };
@@ -107199,8 +107788,8 @@ var require_find_my_way = __commonJS({
107199
107788
  Router.prototype._rebuild = function(routes2) {
107200
107789
  this.reset();
107201
107790
  for (const route of routes2) {
107202
- const { method, path, opts, handler, store: store2 } = route;
107203
- this._on(method, path, opts, handler, store2);
107791
+ const { method, path, opts, handler, store: store3 } = route;
107792
+ this._on(method, path, opts, handler, store3);
107204
107793
  }
107205
107794
  };
107206
107795
  Router.prototype._defaultRoute = function(req, res, ctx) {
@@ -107260,12 +107849,12 @@ var require_find_my_way = __commonJS({
107260
107849
  if (!httpMethods.hasOwnProperty(i)) continue;
107261
107850
  const m = httpMethods[i];
107262
107851
  const methodName = m.toLowerCase();
107263
- Router.prototype[methodName] = function(path, handler, store2) {
107264
- return this.on(m, path, handler, store2);
107852
+ Router.prototype[methodName] = function(path, handler, store3) {
107853
+ return this.on(m, path, handler, store3);
107265
107854
  };
107266
107855
  }
107267
- Router.prototype.all = function(path, handler, store2) {
107268
- this.on(httpMethods, path, handler, store2);
107856
+ Router.prototype.all = function(path, handler, store3) {
107857
+ this.on(httpMethods, path, handler, store3);
107269
107858
  };
107270
107859
  Router.sanitizeUrlPath = function sanitizeUrlPath(url2, useSemicolonDelimiter) {
107271
107860
  const decoded = safeDecodeURI(url2, useSemicolonDelimiter);
@@ -108145,7 +108734,7 @@ var require_dist5 = __commonJS({
108145
108734
  exports.stringifyCookie = stringifyCookie;
108146
108735
  exports.stringifySetCookie = stringifySetCookie;
108147
108736
  exports.serialize = stringifySetCookie;
108148
- exports.parseSetCookie = parseSetCookie;
108737
+ exports.parseSetCookie = parseSetCookie2;
108149
108738
  exports.stringifySetCookie = stringifySetCookie;
108150
108739
  exports.serialize = stringifySetCookie;
108151
108740
  var cookieNameRegExp = /^[\u0021-\u003A\u003C\u003E-\u007E]+$/;
@@ -108282,7 +108871,7 @@ var require_dist5 = __commonJS({
108282
108871
  }
108283
108872
  return str2;
108284
108873
  }
108285
- function parseSetCookie(str2, options) {
108874
+ function parseSetCookie2(str2, options) {
108286
108875
  const dec = options?.decode || decode4;
108287
108876
  const len = str2.length;
108288
108877
  const endIdx = endIndex(str2, 0, len);
@@ -110973,43 +111562,43 @@ var init_serve_ui = __esm({
110973
111562
  });
110974
111563
 
110975
111564
  // ../../apps/engine/src/history-routes.ts
110976
- function registerHistoryRoutes(app2, store2) {
110977
- app2.get("/history", async () => store2.index());
111565
+ function registerHistoryRoutes(app2, store3) {
111566
+ app2.get("/history", async () => store3.index());
110978
111567
  app2.get("/history/runs/:id", async (request3, reply) => {
110979
- const doc = await store2.readRun(request3.params.id);
111568
+ const doc = await store3.readRun(request3.params.id);
110980
111569
  return doc ?? reply.code(404).send({ error: "run not found in history" });
110981
111570
  });
110982
111571
  app2.get("/history/compares/:id", async (request3, reply) => {
110983
- const doc = await store2.readCompare(request3.params.id);
111572
+ const doc = await store3.readCompare(request3.params.id);
110984
111573
  return doc ?? reply.code(404).send({ error: "compare not found in history" });
110985
111574
  });
110986
111575
  app2.patch("/history/runs/:id", async (request3, reply) => {
110987
111576
  const parsed = runPatch.safeParse(request3.body);
110988
111577
  if (!parsed.success) return reply.code(400).send({ error: "invalid update", issues: parsed.error.issues });
110989
- const summary = await store2.updateRun(request3.params.id, parsed.data);
111578
+ const summary = await store3.updateRun(request3.params.id, parsed.data);
110990
111579
  return summary ?? reply.code(404).send({ error: "run not found in history" });
110991
111580
  });
110992
111581
  app2.patch("/history/compares/:id", async (request3, reply) => {
110993
111582
  const parsed = comparePatch.safeParse(request3.body);
110994
111583
  if (!parsed.success) return reply.code(400).send({ error: "invalid update", issues: parsed.error.issues });
110995
- const summary = await store2.updateCompare(request3.params.id, parsed.data);
111584
+ const summary = await store3.updateCompare(request3.params.id, parsed.data);
110996
111585
  return summary ?? reply.code(404).send({ error: "compare not found in history" });
110997
111586
  });
110998
111587
  app2.post("/history/delete", async (request3, reply) => {
110999
111588
  const parsed = removeBody.safeParse(request3.body);
111000
111589
  if (!parsed.success) return reply.code(400).send({ error: "invalid delete", issues: parsed.error.issues });
111001
- return { removed: await store2.remove(parsed.data) };
111590
+ return { removed: await store3.remove(parsed.data) };
111002
111591
  });
111003
111592
  app2.post("/history/import", async (request3, reply) => {
111004
111593
  try {
111005
111594
  const raw = request3.body;
111006
111595
  const parsed = parseExportFile(raw);
111007
111596
  if (parsed.kind === "report") {
111008
- return { kind: "run", summary: await store2.saveRun(parsed.report, { ...raw?.meta, source: "import" }) };
111597
+ return { kind: "run", summary: await store3.saveRun(parsed.report, { ...raw?.meta, source: "import" }) };
111009
111598
  }
111010
111599
  return {
111011
111600
  kind: "compare",
111012
- summary: await store2.saveCompare({ apis: parsed.doc.apis, mode: parsed.doc.mode, name: raw?.meta?.name, source: "import" })
111601
+ summary: await store3.saveCompare({ apis: parsed.doc.apis, mode: parsed.doc.mode, name: raw?.meta?.name, source: "import" })
111013
111602
  };
111014
111603
  } catch (err) {
111015
111604
  return reply.code(400).send({ error: err instanceof Error ? err.message : "Not an APITestCenter report" });
@@ -111020,7 +111609,7 @@ function registerHistoryRoutes(app2, store2) {
111020
111609
  if (!parsed.success) return reply.code(400).send({ error: "invalid compare", issues: parsed.error.issues });
111021
111610
  const doc = { kind: "compare", apis: parsed.data.apis };
111022
111611
  if (!isCompareExport(doc)) return reply.code(400).send({ error: "each API needs a run report" });
111023
- return store2.saveCompare({
111612
+ return store3.saveCompare({
111024
111613
  apis: parsed.data.apis,
111025
111614
  mode: parsed.data.mode,
111026
111615
  name: parsed.data.name,
@@ -111031,7 +111620,7 @@ function registerHistoryRoutes(app2, store2) {
111031
111620
  const parsed = revealBody.safeParse(request3.body ?? {});
111032
111621
  if (!parsed.success) return reply.code(400).send({ error: "invalid target" });
111033
111622
  const { folder, id } = parsed.data;
111034
- return { path: await store2.reveal(folder && id ? { folder, id } : void 0) };
111623
+ return { path: await store3.reveal(folder && id ? { folder, id } : void 0) };
111035
111624
  });
111036
111625
  }
111037
111626
  var runPatch, comparePatch, removeBody, compareBody, revealBody;
@@ -111089,53 +111678,53 @@ var init_settings_routes = __esm({
111089
111678
  });
111090
111679
 
111091
111680
  // ../../apps/engine/src/workspace-routes.ts
111092
- function registerWorkspaceRoutes(app2, store2, onFolderMoved) {
111093
- app2.get("/workspace", async () => store2.index());
111094
- app2.get("/workspace/folders", async (request3) => store2.getFolder(request3.query.path ?? ""));
111095
- app2.get("/workspace/folders/chain", async (request3) => store2.folderChain(request3.query.collection ?? ""));
111681
+ function registerWorkspaceRoutes(app2, store3, onFolderMoved) {
111682
+ app2.get("/workspace", async () => store3.index());
111683
+ app2.get("/workspace/folders", async (request3) => store3.getFolder(request3.query.path ?? ""));
111684
+ app2.get("/workspace/folders/chain", async (request3) => store3.folderChain(request3.query.collection ?? ""));
111096
111685
  app2.put("/workspace/folders", async (request3, reply) => {
111097
111686
  const parsed = external_exports.object({ path: external_exports.string().min(1).max(400), settings: external_exports.unknown() }).strict().safeParse(request3.body);
111098
111687
  if (!parsed.success) return reply.code(400).send({ error: "invalid folder", issues: parsed.error.issues });
111099
- return store2.saveFolder(parsed.data.path, parsed.data.settings);
111688
+ return store3.saveFolder(parsed.data.path, parsed.data.settings);
111100
111689
  });
111101
111690
  app2.post("/workspace/folders/move", async (request3, reply) => {
111102
111691
  const parsed = external_exports.object({ from: external_exports.string().min(1).max(400), to: external_exports.string().min(1).max(400) }).strict().safeParse(request3.body);
111103
111692
  if (!parsed.success) return reply.code(400).send({ error: "invalid move", issues: parsed.error.issues });
111104
- const moved = await store2.moveFolder(parsed.data.from, parsed.data.to);
111693
+ const moved = await store3.moveFolder(parsed.data.from, parsed.data.to);
111105
111694
  await onFolderMoved?.(moved.from, moved.to);
111106
111695
  return moved;
111107
111696
  });
111108
111697
  app2.delete("/workspace/folders", async (request3, reply) => {
111109
- return await store2.deleteFolder(request3.query.path ?? "") ? { removed: 1 } : reply.code(404).send({ error: "folder not found" });
111698
+ return await store3.deleteFolder(request3.query.path ?? "") ? { removed: 1 } : reply.code(404).send({ error: "folder not found" });
111110
111699
  });
111111
111700
  app2.get("/workspace/requests/:id", async (request3, reply) => {
111112
- const saved = await store2.getRequest(request3.params.id);
111701
+ const saved = await store3.getRequest(request3.params.id);
111113
111702
  return saved ?? reply.code(404).send({ error: "saved request not found" });
111114
111703
  });
111115
111704
  app2.post("/workspace/requests", async (request3, reply) => {
111116
111705
  const parsed = requestBody.safeParse(request3.body);
111117
111706
  if (!parsed.success) return reply.code(400).send({ error: "invalid request", issues: parsed.error.issues });
111118
- return store2.saveRequest(parsed.data);
111707
+ return store3.saveRequest(parsed.data);
111119
111708
  });
111120
111709
  app2.put("/workspace/requests/:id", async (request3, reply) => {
111121
111710
  const parsed = requestBody.safeParse(request3.body);
111122
111711
  if (!parsed.success) return reply.code(400).send({ error: "invalid request", issues: parsed.error.issues });
111123
- return store2.saveRequest({ ...parsed.data, id: request3.params.id });
111712
+ return store3.saveRequest({ ...parsed.data, id: request3.params.id });
111124
111713
  });
111125
111714
  app2.delete("/workspace/requests/:id", async (request3, reply) => {
111126
- return await store2.deleteRequest(request3.params.id) ? { removed: 1 } : reply.code(404).send({ error: "saved request not found" });
111715
+ return await store3.deleteRequest(request3.params.id) ? { removed: 1 } : reply.code(404).send({ error: "saved request not found" });
111127
111716
  });
111128
- app2.put("/workspace/environments", async (request3) => store2.saveEnvironments(request3.body));
111717
+ app2.put("/workspace/environments", async (request3) => store3.saveEnvironments(request3.body));
111129
111718
  app2.post("/workspace/import", async (request3, reply) => {
111130
111719
  const parsed = importBody.safeParse(request3.body);
111131
111720
  if (!parsed.success) return reply.code(400).send({ error: "invalid import", issues: parsed.error.issues });
111132
111721
  const saved = [];
111133
- for (const f of parsed.data.folders) await store2.saveFolder(f.path, f.settings);
111134
- for (const r of parsed.data.requests) saved.push(await store2.saveRequest(r));
111722
+ for (const f of parsed.data.folders) await store3.saveFolder(f.path, f.settings);
111723
+ for (const r of parsed.data.requests) saved.push(await store3.saveRequest(r));
111135
111724
  let added = 0;
111136
111725
  let merged = 0;
111137
111726
  if (parsed.data.environments.length) {
111138
- const envs = await store2.environments();
111727
+ const envs = await store3.environments();
111139
111728
  for (const incoming of parsed.data.environments) {
111140
111729
  const existing = envs.find((e) => e.name.toLowerCase() === incoming.name.toLowerCase());
111141
111730
  if (existing) {
@@ -111147,7 +111736,7 @@ function registerWorkspaceRoutes(app2, store2, onFolderMoved) {
111147
111736
  added++;
111148
111737
  }
111149
111738
  }
111150
- await store2.saveEnvironments(envs);
111739
+ await store3.saveEnvironments(envs);
111151
111740
  }
111152
111741
  return { requests: saved.length, firstId: saved[0]?.id ?? null, environments: { added, merged } };
111153
111742
  });
@@ -111178,7 +111767,7 @@ var init_workspace_routes = __esm({
111178
111767
  });
111179
111768
 
111180
111769
  // ../../apps/engine/src/demo-api.ts
111181
- import { randomBytes as randomBytes4 } from "node:crypto";
111770
+ import { randomBytes as randomBytes5 } from "node:crypto";
111182
111771
  function registerDemoApi(api) {
111183
111772
  const tokens = /* @__PURE__ */ new Map();
111184
111773
  const created = /* @__PURE__ */ new Map();
@@ -111204,7 +111793,7 @@ function registerDemoApi(api) {
111204
111793
  return reply.code(401).send({ error: 'Use any username with the password "demo".' });
111205
111794
  }
111206
111795
  if (tokens.size > 5e3) tokens.clear();
111207
- const token = `demo-${randomBytes4(12).toString("hex")}`;
111796
+ const token = `demo-${randomBytes5(12).toString("hex")}`;
111208
111797
  tokens.set(token, username);
111209
111798
  return { data: { token, user: username, expiresIn: 3600 } };
111210
111799
  });
@@ -111598,6 +112187,8 @@ async function routes(api) {
111598
112187
  useOAuthStore(secrets2);
111599
112188
  registerOAuthRoutes(api);
111600
112189
  useCertificateStore(history.dir, secrets2);
112190
+ useCookieStore(secrets2);
112191
+ registerCookieRoutes(api);
111601
112192
  registerCertificateRoutes(api);
111602
112193
  const suitePlans = new SuitePlans(history.dir);
111603
112194
  registerWorkspaceRoutes(api, workspace, (from, to) => suitePlans.move(from, to));
@@ -111639,7 +112230,7 @@ async function routes(api) {
111639
112230
  function engineUrl() {
111640
112231
  return listeningUrl;
111641
112232
  }
111642
- var import_cors, import_dotenv, import_fastify, root, host, port, history, appVersion, usage, app, listeningUrl, uiDir;
112233
+ var import_cors, import_dotenv, import_fastify, root, tlsCheckWasOff, host, port, history, appVersion, usage, app, listeningUrl, uiDir;
111643
112234
  var init_index = __esm({
111644
112235
  async "../../apps/engine/src/index.ts"() {
111645
112236
  "use strict";
@@ -111662,9 +112253,12 @@ var init_index = __esm({
111662
112253
  init_suite_run();
111663
112254
  init_oauth();
111664
112255
  init_certificates();
112256
+ init_cookies();
111665
112257
  init_local_servers();
111666
112258
  root = resolve3(dirname2(fileURLToPath(import.meta.url)), "../../..");
111667
112259
  if (!process.env.ATC_NO_DOTENV) (0, import_dotenv.config)({ path: resolve3(root, ".env") });
112260
+ tlsCheckWasOff = process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0";
112261
+ if (tlsCheckWasOff) delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
111668
112262
  host = process.env.ENGINE_HOST ?? "127.0.0.1";
111669
112263
  port = Number(process.env.ENGINE_PORT ?? 4100);
111670
112264
  history = new HistoryStore();
@@ -111696,6 +112290,7 @@ var init_index = __esm({
111696
112290
  const url2 = `http://${host}:${typeof address === "object" && address ? address.port : port}`;
111697
112291
  listeningUrl = url2;
111698
112292
  app.log.info(`engine listening on ${url2}`);
112293
+ if (tlsCheckWasOff) app.log.warn("NODE_TLS_REJECT_UNAUTHORIZED=0 is set on this computer and was ignored: certificates are checked against the system's trusted certificates; use Skip TLS certificate check on a request to turn it off there");
111699
112294
  if (!process.env.ATC_NO_READY_LINE) console.log(`ATC_READY ${url2}`);
111700
112295
  } catch (err) {
111701
112296
  app.log.error(err);
@@ -111794,10 +112389,11 @@ init_secret_store();
111794
112389
  init_suite_run();
111795
112390
  init_oauth();
111796
112391
  init_certificates();
112392
+ init_cookies();
111797
112393
  init_workspace_store();
111798
112394
  var repoRoot = resolve4(dirname3(fileURLToPath2(import.meta.url)), "../../..");
111799
112395
  var EXIT = { passed: 0, failed: 1, error: 2 };
111800
- var VERSION = "0.7.0";
112396
+ var VERSION = "0.8.0";
111801
112397
  var HELP = `APITestCenter CLI ${VERSION} \u2014 https://apitestcenter.com/docs/cli-and-ci.html
111802
112398
 
111803
112399
  Usage (apitestcenter, or the short name atc once installed)
@@ -111909,6 +112505,7 @@ async function main() {
111909
112505
  const workspace = new WorkspaceStore(dataDir, secrets2);
111910
112506
  useOAuthStore(secrets2);
111911
112507
  useCertificateStore(dataDir, secrets2);
112508
+ useCookieStore(secrets2);
111912
112509
  for (const spec of values.cert ?? []) {
111913
112510
  const eq = spec.indexOf("=");
111914
112511
  const files = eq > 0 ? spec.slice(eq + 1).split(",").map((f) => f.trim()).filter(Boolean) : [];
@@ -111967,7 +112564,7 @@ Environments (${index.environments.length})`);
111967
112564
  const message = err instanceof Error ? err.message : String(err);
111968
112565
  fail(message.replace(/needs an environment.*$/, "is not set \u2014 use --env <name> or --var name=value"));
111969
112566
  }
111970
- let load2 = values.preset ? applyPreset(DEFAULT_LOAD, values.preset) : { ...DEFAULT_LOAD };
112567
+ let load3 = values.preset ? applyPreset(DEFAULT_LOAD, values.preset) : { ...DEFAULT_LOAD };
111971
112568
  const num3 = (v, label) => {
111972
112569
  if (v == null) return void 0;
111973
112570
  const n = Number(v);
@@ -111984,8 +112581,8 @@ Environments (${index.environments.length})`);
111984
112581
  };
111985
112582
  if (custom2.rps != null) custom2.mode = "rate";
111986
112583
  const overrides = Object.fromEntries(Object.entries(custom2).filter(([, v]) => v !== void 0));
111987
- if (Object.keys(overrides).length) load2 = { ...load2, ...overrides, stages: load2.stages && !values.duration ? load2.stages : null, preset: "custom" };
111988
- const problem = loadProblem(load2);
112584
+ if (Object.keys(overrides).length) load3 = { ...load3, ...overrides, stages: load3.stages && !values.duration ? load3.stages : null, preset: "custom" };
112585
+ const problem = loadProblem(load3);
111989
112586
  if (problem) fail(problem);
111990
112587
  const pct3 = (v) => v == null ? void 0 : (num3(v, "percent") ?? 0) / 100;
111991
112588
  const targets = values.p95 || values.p99 || values.errors || values.checks ? { p95Ms: num3(values.p95, "--p95"), p99Ms: num3(values.p99, "--p99"), errorRate: pct3(values.errors), checksRate: pct3(values.checks), abortOnFail: values.abort === true } : void 0;
@@ -112005,7 +112602,7 @@ Environments (${index.environments.length})`);
112005
112602
  if (!Number.isFinite(n) || n < 0) fail("--max-slower must be a percentage, like 10");
112006
112603
  return n / 100;
112007
112604
  })();
112008
- const payload = buildPayload(draft, toLoadConfig(load2), `${name} \xB7 CLI`, targets);
112605
+ const payload = buildPayload(draft, toLoadConfig(load3), `${name} \xB7 CLI`, targets);
112009
112606
  const log = (line) => !values.quiet && console.log(line);
112010
112607
  if (draft.before) {
112011
112608
  const before = draft.before;
@@ -112036,7 +112633,7 @@ Environments (${index.environments.length})`);
112036
112633
  }
112037
112634
  }
112038
112635
  const started = await startRun(labRunSchema.parse(payload), repoRoot).catch((err) => fail(err instanceof Error ? err.message : String(err)));
112039
- log(`load ${payload.request.method} ${displayTemplate(payload.request.url)} \xB7 ${load2.mode === "rate" ? `${load2.rps} req/s` : `${load2.vus} VUs`} \xB7 ${load2.duration}`);
112636
+ log(`load ${payload.request.method} ${displayTemplate(payload.request.url)} \xB7 ${load3.mode === "rate" ? `${load3.rps} req/s` : `${load3.vus} VUs`} \xB7 ${load3.duration}`);
112040
112637
  let interrupted = false;
112041
112638
  process.on("SIGINT", () => {
112042
112639
  if (interrupted) process.exit(EXIT.error);
@@ -112134,9 +112731,9 @@ async function init(collection, folder) {
112134
112731
  fail(`${folder} already has saved requests. Open them with: npx apitestcenter ui --data ${folder}`);
112135
112732
  }
112136
112733
  mkdirSync2(dir2, { recursive: true });
112137
- const store2 = new WorkspaceStore(dir2, new SecretStore(dir2));
112138
- await store2.saveEnvironments([{ id: randomUUID7(), name: "Local", variables: [{ key: "baseUrl", value: "http://localhost:3000", secret: false }] }]);
112139
- await store2.saveRequest({
112734
+ const store3 = new WorkspaceStore(dir2, new SecretStore(dir2));
112735
+ await store3.saveEnvironments([{ id: randomUUID7(), name: "Local", variables: [{ key: "baseUrl", value: "http://localhost:3000", secret: false }] }]);
112736
+ await store3.saveRequest({
112140
112737
  name: "Health check",
112141
112738
  collection: name,
112142
112739
  request: { method: "GET", url: "{{baseUrl}}/health", checks: [{ type: "status", expect: "2xx" }] }