apitestcenter 0.7.0 → 0.9.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 (53) 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 +1064 -364
  4. package/dist/ui/404/index.html +1 -1
  5. package/dist/ui/404.html +1 -1
  6. package/dist/ui/__next.__PAGE__.txt +10 -10
  7. package/dist/ui/__next._full.txt +11 -11
  8. package/dist/ui/__next._tree.txt +2 -2
  9. package/dist/ui/_next/static/chunks/{0txpfp028djv6.js → 13xdai3-mkg7c.js} +66 -65
  10. package/dist/ui/_next/static/chunks/2i7s5kuurlkr6.css +3 -0
  11. package/dist/ui/_next/static/chunks/2od90u8fd_sla.js +18 -0
  12. package/dist/ui/_next/static/chunks/36lx6-56-udwb.js +2 -0
  13. package/dist/ui/_next/static/chunks/3qxq1d9u_kohv.js +1 -0
  14. package/dist/ui/_not-found/__next._full.txt +10 -10
  15. package/dist/ui/_not-found/__next._not-found.__PAGE__.txt +9 -9
  16. package/dist/ui/_not-found/__next._tree.txt +2 -2
  17. package/dist/ui/_not-found/index.html +1 -1
  18. package/dist/ui/_not-found/index.txt +10 -10
  19. package/dist/ui/compare/__next._full.txt +11 -11
  20. package/dist/ui/compare/__next._tree.txt +2 -2
  21. package/dist/ui/compare/__next.compare.__PAGE__.txt +10 -10
  22. package/dist/ui/compare/index.html +1 -1
  23. package/dist/ui/compare/index.txt +11 -11
  24. package/dist/ui/history/__next._full.txt +12 -12
  25. package/dist/ui/history/__next._tree.txt +2 -2
  26. package/dist/ui/history/__next.history.__PAGE__.txt +11 -11
  27. package/dist/ui/history/index.html +1 -1
  28. package/dist/ui/history/index.txt +12 -12
  29. package/dist/ui/index.html +1 -1
  30. package/dist/ui/index.txt +11 -11
  31. package/dist/ui/preview/__next._full.txt +12 -12
  32. package/dist/ui/preview/__next._tree.txt +2 -2
  33. package/dist/ui/preview/__next.preview.__PAGE__.txt +11 -11
  34. package/dist/ui/preview/index.html +1 -1
  35. package/dist/ui/preview/index.txt +12 -12
  36. package/dist/ui/suite/__next._full.txt +11 -11
  37. package/dist/ui/suite/__next._tree.txt +2 -2
  38. package/dist/ui/suite/__next.suite.__PAGE__.txt +10 -10
  39. package/dist/ui/suite/index.html +1 -1
  40. package/dist/ui/suite/index.txt +11 -11
  41. package/dist/ui/tests/__next._full.txt +10 -10
  42. package/dist/ui/tests/__next._tree.txt +2 -2
  43. package/dist/ui/tests/__next.tests.__PAGE__.txt +9 -9
  44. package/dist/ui/tests/index.html +1 -1
  45. package/dist/ui/tests/index.txt +10 -10
  46. package/package.json +1 -1
  47. package/dist/ui/_next/static/chunks/0-898cb98enjv.css +0 -3
  48. package/dist/ui/_next/static/chunks/0kfr8kv4cj_kv.js +0 -1
  49. package/dist/ui/_next/static/chunks/0vkcbl8nakdh1.js +0 -2
  50. package/dist/ui/_next/static/chunks/1w_q4qvvuinj2.js +0 -1
  51. /package/dist/ui/_next/static/{lBYayPKoT6YJhgfHFuw6O → C6zMFzDkVqF8IXny_v7JK}/_buildManifest.js +0 -0
  52. /package/dist/ui/_next/static/{lBYayPKoT6YJhgfHFuw6O → C6zMFzDkVqF8IXny_v7JK}/_clientMiddlewareManifest.js +0 -0
  53. /package/dist/ui/_next/static/{lBYayPKoT6YJhgfHFuw6O → C6zMFzDkVqF8IXny_v7JK}/_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
 
@@ -16010,7 +16070,7 @@ function checkLabel(check2) {
16010
16070
  case "body":
16011
16071
  return `body ${check2.op === "contains" ? "contains" : "does not contain"} "${clip(check2.value)}"`;
16012
16072
  case "json":
16013
- return check2.op === "exists" ? `${jsonPathLabel(check2.path)} exists` : `${jsonPathLabel(check2.path)} ${check2.op === "equals" ? "=" : check2.op === "not_equals" ? "\u2260" : "contains"} "${clip(check2.value)}"`;
16073
+ return check2.op === "exists" || check2.op === "not_exists" ? `${jsonPathLabel(check2.path)} ${check2.op === "exists" ? "exists" : "does not exist"}` : `${jsonPathLabel(check2.path)} ${check2.op === "equals" ? "=" : check2.op === "not_equals" ? "\u2260" : "contains"} "${clip(check2.value)}"`;
16014
16074
  case "header":
16015
16075
  return check2.op === "exists" ? `header ${check2.name} exists` : `header ${check2.name} ${check2.op === "equals" ? "=" : "contains"} "${clip(check2.value)}"`;
16016
16076
  case "time":
@@ -16055,7 +16115,7 @@ var init_checks3 = __esm({
16055
16115
  external_exports.object({
16056
16116
  type: external_exports.literal("json"),
16057
16117
  path: external_exports.string().min(1).max(300),
16058
- op: external_exports.enum(["exists", "equals", "not_equals", "contains"]),
16118
+ op: external_exports.enum(["exists", "not_exists", "equals", "not_equals", "contains"]),
16059
16119
  value: external_exports.string().max(2e3).default("")
16060
16120
  }).strict(),
16061
16121
  external_exports.object({
@@ -16118,6 +16178,7 @@ function __atcCheck(check, res) {
16118
16178
  try { parsed = JSON.parse(res.body); } catch (e) { return false; }
16119
16179
  var got = __atcJsonGet(parsed, check.path);
16120
16180
  if (check.op === "exists") return got.found;
16181
+ if (check.op === "not_exists") return !got.found;
16121
16182
  if (!got.found) return check.op === "not_equals";
16122
16183
  var text = __atcAsText(got.value);
16123
16184
  if (check.op === "equals") return text === check.value;
@@ -16307,18 +16368,18 @@ var init_definition = __esm({
16307
16368
  thinkTimeMs: external_exports.number().min(0).default(0),
16308
16369
  thinkJitterMs: external_exports.number().min(0).default(0),
16309
16370
  gracefulStop: durationSchema.default("10s")
16310
- }).strict().superRefine((load2, ctx) => {
16311
- if ((load2.executor === "ramping-vus" || load2.executor === "ramping-arrival-rate") && load2.stages.length === 0) {
16371
+ }).strict().superRefine((load3, ctx) => {
16372
+ if ((load3.executor === "ramping-vus" || load3.executor === "ramping-arrival-rate") && load3.stages.length === 0) {
16312
16373
  ctx.addIssue({
16313
16374
  code: external_exports.ZodIssueCode.custom,
16314
- message: `${load2.executor} requires at least one stage`,
16375
+ message: `${load3.executor} requires at least one stage`,
16315
16376
  path: ["stages"]
16316
16377
  });
16317
16378
  }
16318
- if (load2.executor === "constant-arrival-rate" && !load2.rate) {
16379
+ if (load3.executor === "constant-arrival-rate" && !load3.rate) {
16319
16380
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: "constant-arrival-rate needs a rate", path: ["rate"] });
16320
16381
  }
16321
- if (!isArrivalRate(load2.executor) && load2.stages.some((s) => s.target > 500)) {
16382
+ if (!isArrivalRate(load3.executor) && load3.stages.some((s) => s.target > 500)) {
16322
16383
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: "A stage can use at most 500 virtual users", path: ["stages"] });
16323
16384
  }
16324
16385
  });
@@ -16341,9 +16402,11 @@ var init_definition = __esm({
16341
16402
  maxRedirects: external_exports.number().int().min(0).max(20).default(5),
16342
16403
  httpVersion: external_exports.enum(["1.1", "2"]).default("1.1"),
16343
16404
  tlsSkipVerify: external_exports.boolean().default(false),
16344
- connectionReuse: external_exports.boolean().default(true)
16405
+ connectionReuse: external_exports.boolean().default(true),
16406
+ /** Send cookies from the jar and keep the ones the API sets (like a browser). */
16407
+ cookies: external_exports.boolean().default(true)
16345
16408
  }).strict();
16346
- oauthGrants = ["client_credentials", "password", "authorization_code"];
16409
+ oauthGrants = ["client_credentials", "password", "authorization_code", "jwt_bearer", "saml_bearer"];
16347
16410
  oauthConfigSchema = external_exports.object({
16348
16411
  grant: external_exports.enum(oauthGrants),
16349
16412
  tokenUrl: external_exports.string().url(),
@@ -16355,7 +16418,25 @@ var init_definition = __esm({
16355
16418
  username: external_exports.string().default(""),
16356
16419
  password: external_exports.string().default(""),
16357
16420
  /** How the client id and secret go to the token endpoint. */
16358
- clientAuth: external_exports.enum(["basic", "body"]).default("basic"),
16421
+ /** basic / body: the client secret; private_key_jwt: a JWT signed with the private key (client assertion). */
16422
+ clientAuth: external_exports.enum(["basic", "body", "private_key_jwt"]).default("basic"),
16423
+ // Signed assertions (JWT bearer grant, client assertion).
16424
+ privateKey: external_exports.string().default(""),
16425
+ assertionAlgorithm: external_exports.enum(["RS256", "RS384", "RS512", "PS256", "ES256", "ES384"]).default("RS256"),
16426
+ issuer: external_exports.string().default(""),
16427
+ subject: external_exports.string().default(""),
16428
+ audience: external_exports.string().default(""),
16429
+ kid: external_exports.string().default(""),
16430
+ /** The certificate (PEM) of the key: its thumbprint goes in the JWT header (x5t), as Azure AD wants. */
16431
+ certificate: external_exports.string().default(""),
16432
+ /** More claims for the JWT bearer assertion, as JSON. */
16433
+ extraClaims: external_exports.string().default(""),
16434
+ // SAML 2.0 bearer (SAP SuccessFactors).
16435
+ samlSource: external_exports.enum(["idp", "assertion"]).default("idp"),
16436
+ idpUrl: external_exports.string().default(""),
16437
+ userId: external_exports.string().default(""),
16438
+ companyId: external_exports.string().default(""),
16439
+ assertion: external_exports.string().default(""),
16359
16440
  pkce: external_exports.boolean().default(true),
16360
16441
  /** Extra token-request fields, e.g. audience or resource. */
16361
16442
  extra: external_exports.array(keyValueSchema).max(20).default([])
@@ -16376,6 +16457,17 @@ var init_definition = __esm({
16376
16457
  headerName: external_exports.string().min(1).default("Authorization"),
16377
16458
  prefix: external_exports.string().default("Bearer")
16378
16459
  }).strict(),
16460
+ // OAuth 1.0 (RFC 5849): every request signed with the consumer and token secrets (NetSuite TBA…).
16461
+ external_exports.object({
16462
+ mode: external_exports.literal("oauth1"),
16463
+ consumerKey: external_exports.string().min(1),
16464
+ consumerSecret: external_exports.string(),
16465
+ token: external_exports.string().default(""),
16466
+ tokenSecret: external_exports.string().default(""),
16467
+ signatureMethod: external_exports.enum(["HMAC-SHA256", "HMAC-SHA1", "PLAINTEXT"]).default("HMAC-SHA256"),
16468
+ realm: external_exports.string().default(""),
16469
+ addTo: external_exports.enum(["header", "query"]).default("header")
16470
+ }).strict(),
16379
16471
  // AWS Signature Version 4: every request is signed (API Gateway, S3, Lambda function URLs…).
16380
16472
  external_exports.object({
16381
16473
  mode: external_exports.literal("aws_sigv4"),
@@ -24007,9 +24099,14 @@ function defaultRequestDraft() {
24007
24099
  params: [],
24008
24100
  headers: [],
24009
24101
  bodyMode: "none",
24102
+ graphqlQuery: "",
24103
+ graphqlVariables: "",
24104
+ graphqlOperation: "",
24105
+ graphqlErrorsFail: true,
24010
24106
  bodyText: "",
24011
24107
  authMode: "inherit",
24012
24108
  oauth: defaultOAuth(),
24109
+ oauth1: defaultOAuth1(),
24013
24110
  aws: defaultAws(),
24014
24111
  jwt: defaultJwt(),
24015
24112
  domain: "",
@@ -24022,6 +24119,7 @@ function defaultRequestDraft() {
24022
24119
  timeout: "60s",
24023
24120
  maxRedirects: 5,
24024
24121
  connectionReuse: true,
24122
+ useCookies: true,
24025
24123
  tlsSkip: false,
24026
24124
  checks: [],
24027
24125
  before: null,
@@ -24064,8 +24162,8 @@ function requestFromUnknown(value) {
24064
24162
  const base = defaultRequestDraft();
24065
24163
  if (!value || typeof value !== "object") return base;
24066
24164
  const item = value;
24067
- 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";
24165
+ const bodyMode = item.bodyMode === "json" || item.bodyMode === "text" || item.bodyMode === "graphql" ? item.bodyMode : "none";
24166
+ 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
24167
  return {
24070
24168
  method: METHODS.includes(item.method) ? item.method : "GET",
24071
24169
  url: str(item.url, ""),
@@ -24075,8 +24173,13 @@ function requestFromUnknown(value) {
24075
24173
  headers: rows(item.headers),
24076
24174
  bodyMode,
24077
24175
  bodyText: str(item.bodyText, ""),
24176
+ graphqlQuery: str(item.graphqlQuery, ""),
24177
+ graphqlVariables: str(item.graphqlVariables, ""),
24178
+ graphqlOperation: str(item.graphqlOperation, ""),
24179
+ graphqlErrorsFail: item.graphqlErrorsFail !== false,
24078
24180
  authMode: authMode2,
24079
24181
  oauth: oauthFromUnknown(item.oauth),
24182
+ oauth1: oauth1FromUnknown(item.oauth1),
24080
24183
  aws: awsFromUnknown(item.aws),
24081
24184
  jwt: jwtFromUnknown(item.jwt),
24082
24185
  domain: str(item.domain, ""),
@@ -24089,6 +24192,7 @@ function requestFromUnknown(value) {
24089
24192
  timeout: str(item.timeout, base.timeout),
24090
24193
  maxRedirects: num(item.maxRedirects, base.maxRedirects),
24091
24194
  connectionReuse: item.connectionReuse !== false,
24195
+ useCookies: item.useCookies !== false,
24092
24196
  tlsSkip: item.tlsSkip === true,
24093
24197
  checks: checksFrom(item.checks),
24094
24198
  before: beforeFrom(item.before),
@@ -24099,12 +24203,12 @@ function requestFromUnknown(value) {
24099
24203
  })
24100
24204
  };
24101
24205
  }
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] };
24206
+ function applyPreset(load3, preset) {
24207
+ if (preset === "custom") return { ...load3, preset };
24208
+ return { preset, mode: "users", rps: load3.rps, maxVus: load3.maxVus, stages: null, ...LOAD_KNOB_PRESETS[preset] };
24105
24209
  }
24106
- function toLoadConfig(load2) {
24107
- const { preset: _preset, ...knobs } = load2;
24210
+ function toLoadConfig(load3) {
24211
+ const { preset: _preset, ...knobs } = load3;
24108
24212
  return loadFromLabKnobs(knobs);
24109
24213
  }
24110
24214
  function withUrl(draft, value) {
@@ -24119,12 +24223,12 @@ function hasBody(method) {
24119
24223
  function draftRuntimeNames(draft) {
24120
24224
  return runtimeVariableNames({ setup: draft.before, data: draft.testData });
24121
24225
  }
24122
- function buildPayload(draft, load2, name, targets) {
24226
+ function buildPayload(draft, load3, name, targets) {
24123
24227
  const parts = requestParts(draft);
24124
24228
  return {
24125
24229
  name: name.slice(0, 120),
24126
24230
  ...parts,
24127
- load: load2,
24231
+ load: load3,
24128
24232
  targets,
24129
24233
  setup: void 0,
24130
24234
  data: draft.testData && draft.testData.rows.length ? draft.testData : void 0,
@@ -24178,6 +24282,11 @@ function requestParts(draft) {
24178
24282
  if (!a.accessKeyId.trim() || !a.secretAccessKey) throw new Error("AWS signing needs the access key ID and the secret access key");
24179
24283
  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
24284
  auth = { mode: "aws_sigv4", accessKeyId: a.accessKeyId.trim(), secretAccessKey: a.secretAccessKey, sessionToken: a.sessionToken.trim(), region: a.region.trim(), service: a.service.trim() };
24285
+ } else if (draft.authMode === "oauth1") {
24286
+ const o = draft.oauth1;
24287
+ if (!o.consumerKey.trim()) throw new Error("OAuth 1.0 needs the consumer key");
24288
+ if (o.token.trim() && !o.tokenSecret) throw new Error("OAuth 1.0: the token needs its token secret");
24289
+ 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
24290
  } else if (draft.authMode === "jwt") {
24182
24291
  const j = draft.jwt;
24183
24292
  const hmac3 = j.algorithm.startsWith("HS");
@@ -24200,6 +24309,17 @@ function requestParts(draft) {
24200
24309
  if (bare) throw new Error(`Put quotes around {{${bare}}} in the JSON body \u2014 values from test data and run-first steps are filled in as text`);
24201
24310
  throw new Error(`Body is not valid JSON: ${err instanceof Error ? err.message : "parse error"}`);
24202
24311
  }
24312
+ } else if (hasBody(draft.method) && draft.bodyMode === "graphql") {
24313
+ if (!draft.graphqlQuery.trim()) throw new Error("Write the GraphQL query");
24314
+ let variables = {};
24315
+ if (draft.graphqlVariables.trim()) {
24316
+ try {
24317
+ variables = JSON.parse(draft.graphqlVariables);
24318
+ } catch (err) {
24319
+ throw new Error(`GraphQL variables are not valid JSON: ${err instanceof Error ? err.message : "parse error"} \u2014 put quotes around "{{values}}" from test data`);
24320
+ }
24321
+ }
24322
+ body = { mode: "json", json: { query: draft.graphqlQuery, variables, ...draft.graphqlOperation.trim() ? { operationName: draft.graphqlOperation.trim() } : {} } };
24203
24323
  } else if (hasBody(draft.method) && draft.bodyMode === "text") {
24204
24324
  body = { mode: "text", text: draft.bodyText };
24205
24325
  }
@@ -24211,7 +24331,11 @@ function requestParts(draft) {
24211
24331
  headers: draft.headers.filter((h) => h.key.trim()),
24212
24332
  body,
24213
24333
  expectedStatus: [],
24214
- checks: usableChecks(draft.checks)
24334
+ // GraphQL answers 200 even when the query failed; "errors" in the answer is the failure.
24335
+ checks: [
24336
+ ...usableChecks(draft.checks),
24337
+ ...hasBody(draft.method) && draft.bodyMode === "graphql" && draft.graphqlErrorsFail ? [{ type: "json", path: "errors", op: "not_exists", value: "" }] : []
24338
+ ]
24215
24339
  },
24216
24340
  auth,
24217
24341
  options: {
@@ -24219,7 +24343,8 @@ function requestParts(draft) {
24219
24343
  maxRedirects: draft.maxRedirects,
24220
24344
  httpVersion: "1.1",
24221
24345
  tlsSkipVerify: draft.tlsSkip,
24222
- connectionReuse: draft.connectionReuse
24346
+ connectionReuse: draft.connectionReuse,
24347
+ cookies: draft.useCookies
24223
24348
  }
24224
24349
  };
24225
24350
  }
@@ -24250,22 +24375,22 @@ function durationError(value, allowZero = true) {
24250
24375
  if (!allowZero && /^0+(\.0+)?(ms|s|m|h)$/.test(value.trim())) return "Must be longer than 0";
24251
24376
  return void 0;
24252
24377
  }
24253
- function loadProblem(load2) {
24254
- if (load2.stages && load2.stages.length) {
24255
- for (const [i, stage] of load2.stages.entries()) {
24378
+ function loadProblem(load3) {
24379
+ if (load3.stages && load3.stages.length) {
24380
+ for (const [i, stage] of load3.stages.entries()) {
24256
24381
  const bad = durationError(stage.duration, false);
24257
24382
  if (bad) return `Stage ${i + 1}: ${bad}`;
24258
24383
  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`;
24384
+ if (load3.mode !== "rate" && stage.target > 500) return `Stage ${i + 1}: at most 500 virtual users`;
24260
24385
  }
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;
24386
+ if (!load3.stages.some((s) => s.target > 0)) return "At least one stage needs a target above 0";
24387
+ return durationError(load3.gracefulStop) ?? null;
24263
24388
  }
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;
24389
+ if (load3.mode === "rate") {
24390
+ if (!(load3.rps >= 1 && load3.rps <= 1e5)) return "Requests per second must be 1\u2013100,000";
24391
+ if (!(load3.maxVus >= 1 && load3.maxVus <= 2e3)) return "Max virtual users must be 1\u20132,000";
24392
+ } else if (!(load3.vus >= 1 && load3.vus <= 500)) return "Virtual users must be 1\u2013500";
24393
+ return durationError(load3.duration, false) ?? durationError(load3.rampUp) ?? durationError(load3.rampDown) ?? durationError(load3.gracefulStop) ?? null;
24269
24394
  }
24270
24395
  function applyFolders(draft, folders) {
24271
24396
  let next = draft;
@@ -24275,7 +24400,7 @@ function applyFolders(draft, folders) {
24275
24400
  const a = from?.settings.auth;
24276
24401
  if (from && a && a.authMode !== "none") {
24277
24402
  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 };
24403
+ 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
24404
  } else {
24280
24405
  next = { ...next, authMode: "none" };
24281
24406
  }
@@ -24298,6 +24423,20 @@ function oauthWire(o) {
24298
24423
  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
24424
  }
24300
24425
  if (!o.clientId.trim()) throw new Error("OAuth 2.0 needs a client ID");
24426
+ const needsKey = o.grant === "jwt_bearer" || o.clientAuth === "private_key_jwt" || o.grant === "saml_bearer" && o.samlSource === "idp";
24427
+ if (needsKey && !o.privateKey.trim()) throw new Error("OAuth 2.0: this sign-in needs the private key (PEM)");
24428
+ if (o.grant === "jwt_bearer" && o.extraClaims.trim()) {
24429
+ try {
24430
+ const c = JSON.parse(o.extraClaims);
24431
+ if (!c || typeof c !== "object" || Array.isArray(c)) throw new Error("x");
24432
+ } catch {
24433
+ throw new Error("OAuth 2.0: the extra claims must be a JSON object");
24434
+ }
24435
+ }
24436
+ if (o.grant === "saml_bearer" && o.samlSource === "idp" && (!/^https?:\/\//i.test(o.idpUrl.trim()) || !o.userId.trim())) {
24437
+ throw new Error("SAML bearer needs the assertion URL (SuccessFactors: https://<api-server>/oauth/idp) and the user ID");
24438
+ }
24439
+ if (o.grant === "saml_bearer" && o.samlSource === "assertion" && !o.assertion.trim()) throw new Error("SAML bearer needs the assertion (base64)");
24301
24440
  return {
24302
24441
  grant: o.grant,
24303
24442
  tokenUrl,
@@ -24310,7 +24449,20 @@ function oauthWire(o) {
24310
24449
  password: o.password,
24311
24450
  clientAuth: o.clientAuth,
24312
24451
  pkce: o.pkce,
24313
- extra: o.extra.filter((e) => e.key.trim())
24452
+ extra: o.extra.filter((e) => e.key.trim()),
24453
+ privateKey: o.privateKey.trim(),
24454
+ assertionAlgorithm: o.assertionAlgorithm,
24455
+ issuer: o.issuer.trim(),
24456
+ subject: o.subject.trim(),
24457
+ audience: o.audience.trim(),
24458
+ kid: o.kid.trim(),
24459
+ certificate: o.certificate.trim(),
24460
+ extraClaims: o.extraClaims.trim(),
24461
+ samlSource: o.samlSource,
24462
+ idpUrl: o.idpUrl.trim(),
24463
+ userId: o.userId.trim(),
24464
+ companyId: o.companyId.trim(),
24465
+ assertion: o.assertion.trim()
24314
24466
  };
24315
24467
  }
24316
24468
  var METHODS, DEFAULT_LOAD, str, num, TEMPLATED, DURATION;
@@ -24334,12 +24486,15 @@ var init_draft = __esm({
24334
24486
  "params",
24335
24487
  "headers",
24336
24488
  "bodyText",
24489
+ "graphqlQuery",
24490
+ "graphqlVariables",
24337
24491
  "token",
24338
24492
  "username",
24339
24493
  "password",
24340
24494
  "apiName",
24341
24495
  "apiValue",
24342
24496
  "oauth",
24497
+ "oauth1",
24343
24498
  "aws",
24344
24499
  "jwt",
24345
24500
  "domain"
@@ -25189,10 +25344,10 @@ function checksRate(r) {
25189
25344
  return passes + fails ? passes / (passes + fails) : null;
25190
25345
  }
25191
25346
  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);
25347
+ const load3 = r.definition.load;
25348
+ const rate = load3.executor.endsWith("arrival-rate");
25349
+ const peak = rate ? load3.rate ?? Math.max(0, ...load3.stages.map((s) => s.target)) : Math.max(0, ...load3.stages.map((s) => s.target));
25350
+ const ms3 = load3.stages.reduce((n, s) => n + durationToMs(s.duration), 0);
25196
25351
  return { rate, peak, ms: ms3 };
25197
25352
  }
25198
25353
  function fairness(before, after) {
@@ -25473,6 +25628,90 @@ function __atcSign(method, url, headers, body) {
25473
25628
  }
25474
25629
  });
25475
25630
 
25631
+ // ../shared/src/oauth1.ts
25632
+ function pctEncode(s) {
25633
+ return encodeURIComponent(s).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
25634
+ }
25635
+ function decodeForm(s) {
25636
+ try {
25637
+ return decodeURIComponent(s.replace(/\+/g, " "));
25638
+ } catch {
25639
+ return s;
25640
+ }
25641
+ }
25642
+ function formPairs(text) {
25643
+ return text.replace(/^\?/, "").split("&").filter(Boolean).map((p) => {
25644
+ const i = p.indexOf("=");
25645
+ return [decodeForm(i < 0 ? p : p.slice(0, i)), decodeForm(i < 0 ? "" : p.slice(i + 1))];
25646
+ });
25647
+ }
25648
+ function oauth1BaseString(method, url2, params) {
25649
+ const m = /^(https?):\/\/([^/?#]+)([^?#]*)/i.exec(url2);
25650
+ if (!m) throw new Error("OAuth 1.0 needs a full http(s) address");
25651
+ const scheme = m[1].toLowerCase();
25652
+ let host2 = m[2].toLowerCase();
25653
+ if (scheme === "https" && host2.endsWith(":443") || scheme === "http" && host2.endsWith(":80")) host2 = host2.replace(/:\d+$/, "");
25654
+ const base = `${scheme}://${host2}${m[3] || "/"}`;
25655
+ 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("&");
25656
+ return [method.toUpperCase(), pctEncode(base), pctEncode(normalized)].join("&");
25657
+ }
25658
+ function oauth1Params(c, nonce, timestamp) {
25659
+ const p = [
25660
+ ["oauth_consumer_key", c.consumerKey],
25661
+ ["oauth_nonce", nonce],
25662
+ ["oauth_signature_method", c.signatureMethod],
25663
+ ["oauth_timestamp", String(timestamp)],
25664
+ ["oauth_version", "1.0"]
25665
+ ];
25666
+ if (c.token) p.push(["oauth_token", c.token]);
25667
+ return p;
25668
+ }
25669
+ function oauth1SigningKey(c) {
25670
+ return `${pctEncode(c.consumerSecret)}&${pctEncode(c.tokenSecret)}`;
25671
+ }
25672
+ function oauth1Header(c, oauth) {
25673
+ const parts = oauth.map(([k, v]) => `${pctEncode(k)}="${pctEncode(v)}"`);
25674
+ return `OAuth ${c.realm ? `realm="${c.realm.replace(/"/g, "")}", ` : ""}${parts.join(", ")}`;
25675
+ }
25676
+ var OAUTH1_JS;
25677
+ var init_oauth1 = __esm({
25678
+ "../shared/src/oauth1.ts"() {
25679
+ "use strict";
25680
+ OAUTH1_JS = `
25681
+ function __atcPct(s) { return encodeURIComponent(s).replace(/[!'()*]/g, function (c) { return "%" + c.charCodeAt(0).toString(16).toUpperCase(); }); }
25682
+ 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))]; }); }
25683
+ function __atcOAuth1(method, url, headers, body) {
25684
+ var c = OAUTH1;
25685
+ var m = /^(https?):\\/\\/([^/?#]+)([^?#]*)(\\?[^#]*)?/i.exec(url);
25686
+ var scheme = m[1].toLowerCase(), host = m[2].toLowerCase();
25687
+ if ((scheme === "https" && /:443$/.test(host)) || (scheme === "http" && /:80$/.test(host))) host = host.replace(/:\\d+$/, "");
25688
+ var nonce = ""; var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
25689
+ for (var i = 0; i < 32; i++) nonce += abc.charAt(Math.floor(Math.random() * abc.length));
25690
+ 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"]];
25691
+ if (c.token) oauth.push(["oauth_token", c.token]);
25692
+ var params = oauth.concat(__atcForm(m[4] || ""));
25693
+ var ct = ""; for (var k in headers) if (k.toLowerCase() === "content-type") ct = String(headers[k]);
25694
+ if (body && ct.indexOf("application/x-www-form-urlencoded") === 0) params = params.concat(__atcForm(body));
25695
+ 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("&");
25696
+ var baseString = [method.toUpperCase(), __atcPct(scheme + "://" + host + (m[3] || "/")), __atcPct(norm)].join("&");
25697
+ var key = __atcPct(c.consumerSecret) + "&" + __atcPct(c.tokenSecret);
25698
+ var sig = c.signatureMethod === "PLAINTEXT" ? key : crypto.hmac(c.signatureMethod === "HMAC-SHA1" ? "sha1" : "sha256", key, baseString, "base64");
25699
+ oauth.push(["oauth_signature", sig]);
25700
+ if (c.addTo === "query") return url + (m[4] ? "&" : "?") + oauth.map(function (p) { return __atcPct(p[0]) + "=" + __atcPct(p[1]); }).join("&");
25701
+ headers.Authorization = "OAuth " + (c.realm ? 'realm="' + c.realm.replace(/"/g, "") + '", ' : "") + oauth.map(function (p) { return __atcPct(p[0]) + '="' + __atcPct(p[1]) + '"'; }).join(", ");
25702
+ return url;
25703
+ }
25704
+ `;
25705
+ }
25706
+ });
25707
+
25708
+ // ../shared/src/graphql.ts
25709
+ var init_graphql = __esm({
25710
+ "../shared/src/graphql.ts"() {
25711
+ "use strict";
25712
+ }
25713
+ });
25714
+
25476
25715
  // ../shared/src/export-fields.ts
25477
25716
  function roundN(n, digits = 2) {
25478
25717
  if (!Number.isFinite(n)) return 0;
@@ -26429,6 +26668,8 @@ var init_src = __esm({
26429
26668
  init_before_after();
26430
26669
  init_ci_templates();
26431
26670
  init_sigv4();
26671
+ init_oauth1();
26672
+ init_graphql();
26432
26673
  init_latency_bins();
26433
26674
  init_checks3();
26434
26675
  init_definition();
@@ -26878,21 +27119,21 @@ function k6Thresholds(targets, hasChecks) {
26878
27119
  if (hasChecks && targets.checksRate != null) out.checks = [wrap(`rate>=${targets.checksRate}`)];
26879
27120
  return out;
26880
27121
  }
26881
- function scenarioBlock(load2) {
26882
- const common = { gracefulStop: load2.gracefulStop };
26883
- switch (load2.executor) {
27122
+ function scenarioBlock(load3) {
27123
+ const common = { gracefulStop: load3.gracefulStop };
27124
+ switch (load3.executor) {
26884
27125
  case "constant-vus":
26885
- return { executor: "constant-vus", vus: load2.vus, duration: load2.duration, ...common };
27126
+ return { executor: "constant-vus", vus: load3.vus, duration: load3.duration, ...common };
26886
27127
  case "ramping-vus":
26887
- return { executor: "ramping-vus", startVUs: load2.startVUs ?? 0, stages: load2.stages, ...common };
27128
+ return { executor: "ramping-vus", startVUs: load3.startVUs ?? 0, stages: load3.stages, ...common };
26888
27129
  case "constant-arrival-rate":
26889
27130
  return {
26890
27131
  executor: "constant-arrival-rate",
26891
- rate: load2.rate,
27132
+ rate: load3.rate,
26892
27133
  timeUnit: "1s",
26893
- duration: load2.duration,
26894
- preAllocatedVUs: load2.vus,
26895
- maxVUs: Math.max(load2.vus, load2.maxVUs ?? load2.vus),
27134
+ duration: load3.duration,
27135
+ preAllocatedVUs: load3.vus,
27136
+ maxVUs: Math.max(load3.vus, load3.maxVUs ?? load3.vus),
26896
27137
  ...common
26897
27138
  };
26898
27139
  case "ramping-arrival-rate":
@@ -26900,9 +27141,9 @@ function scenarioBlock(load2) {
26900
27141
  executor: "ramping-arrival-rate",
26901
27142
  startRate: 0,
26902
27143
  timeUnit: "1s",
26903
- preAllocatedVUs: load2.vus,
26904
- maxVUs: Math.max(load2.vus, load2.maxVUs ?? load2.vus),
26905
- stages: load2.stages,
27144
+ preAllocatedVUs: load3.vus,
27145
+ maxVUs: Math.max(load3.vus, load3.maxVUs ?? load3.vus),
27146
+ stages: load3.stages,
26906
27147
  ...common
26907
27148
  };
26908
27149
  }
@@ -26950,13 +27191,20 @@ function generateK6Script(run2, extra2 = {}) {
26950
27191
  }
26951
27192
  if (run2.auth.mode === "bearer") {
26952
27193
  env.ATC_TOKEN = normalizeBearerToken(run2.auth.token);
26953
- if (extra2.oauthRefresh) env.ATC_OAUTH = JSON.stringify(extra2.oauthRefresh);
27194
+ if (extra2.oauthRefresh) {
27195
+ const { bodies, ...plan } = extra2.oauthRefresh;
27196
+ env.ATC_OAUTH = JSON.stringify(bodies ? { ...plan, start: bodies.start, stepSec: bodies.stepSec } : plan);
27197
+ if (bodies) files["oauth.json"] = JSON.stringify(bodies.list);
27198
+ }
26954
27199
  } else if (run2.auth.mode === "basic") {
26955
27200
  env.ATC_BASIC_USER = run2.auth.username;
26956
27201
  env.ATC_BASIC_PASS = run2.auth.password;
26957
27202
  } else if (run2.auth.mode === "api_key" && run2.auth.in === "header") {
26958
27203
  env.ATC_API_NAME = run2.auth.name;
26959
27204
  env.ATC_API_KEY = run2.auth.value;
27205
+ } else if (run2.auth.mode === "oauth1") {
27206
+ const { mode: _mode, ...creds } = run2.auth;
27207
+ env.ATC_OAUTH1 = JSON.stringify(creds);
26960
27208
  } else if (run2.auth.mode === "aws_sigv4") {
26961
27209
  const { mode: _mode, ...creds } = run2.auth;
26962
27210
  env.ATC_AWS = JSON.stringify(creds);
@@ -26990,7 +27238,7 @@ function __atcRow() {
26990
27238
  const script = `import http from "k6/http";
26991
27239
  import { check, sleep } from "k6";
26992
27240
  import encoding from "k6/encoding";
26993
- ${run2.auth.mode === "aws_sigv4" ? `import crypto from "k6/crypto";
27241
+ ${run2.auth.mode === "aws_sigv4" || run2.auth.mode === "oauth1" ? `import crypto from "k6/crypto";
26994
27242
  ` : ""}${data ? `import exec from "k6/execution";
26995
27243
  import { SharedArray } from "k6/data";
26996
27244
  ` : ""}
@@ -26999,18 +27247,23 @@ export const options = Object.assign(${optionsBlock(run2)}, __ENV.ATC_TLS_AUTH ?
26999
27247
  ${CHECK_EVALUATOR_JS}
27000
27248
  ${dataBlock}${run2.auth.mode === "aws_sigv4" ? `
27001
27249
  const AWS = JSON.parse(__ENV.ATC_AWS);
27002
- ${SIGV4_JS}` : ""}
27250
+ ${SIGV4_JS}` : ""}${run2.auth.mode === "oauth1" ? `
27251
+ const OAUTH1 = JSON.parse(__ENV.ATC_OAUTH1);
27252
+ ${OAUTH1_JS}` : ""}
27003
27253
  const CHECKS = JSON.parse(__ENV.ATC_CHECKS || "[]");
27004
27254
  // JWT: tokens signed by the app for the whole run; each is used for its stretch of time.
27005
27255
  const JWT = __ENV.ATC_JWT ? JSON.parse(__ENV.ATC_JWT) : null;
27006
27256
  const JWTS = JWT ? JSON.parse(open("./jwt.json")) : [];
27007
27257
  // OAuth 2.0: each simulated user gets a new token shortly before the current one runs out.
27008
27258
  const OAUTH = __ENV.ATC_OAUTH ? JSON.parse(__ENV.ATC_OAUTH) : null;
27259
+ const OAUTH_BODIES = OAUTH && OAUTH.stepSec ? JSON.parse(open("./oauth.json")) : null;
27009
27260
  let __atcToken = __ENV.ATC_TOKEN || "";
27010
27261
  let __atcExpires = OAUTH ? OAUTH.expiresAt : 0;
27011
27262
  function __atcFreshToken() {
27012
27263
  if (OAUTH && Date.now() > __atcExpires - 30000) {
27013
- const r = http.post(OAUTH.tokenUrl, OAUTH.body, { headers: OAUTH.headers, tags: { name: "OAuth token" } });
27264
+ 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;
27265
+ // responseType: the run may discard bodies to save memory, but the token is in this one.
27266
+ const r = http.post(OAUTH.tokenUrl, body, { headers: OAUTH.headers, responseType: "text", tags: { name: "OAuth token" } });
27014
27267
  let json = null;
27015
27268
  try { json = r.json(); } catch (e) { json = null; }
27016
27269
  if (r.status >= 200 && r.status < 300 && json && json.access_token) {
@@ -27028,12 +27281,14 @@ export default function () {
27028
27281
  ${data ? 'const vars = __atcRow();\n const f = function (text, mode) { return __atcFill(text, vars, mode || "raw"); };' : "const f = function (text) { return text; };"}
27029
27282
  const headers = JSON.parse(__ENV.ATC_HEADERS || "{}");
27030
27283
  for (const key in headers) headers[key] = f(headers[key]);
27284
+ if (__ENV.ATC_COOKIE) headers.Cookie = __ENV.ATC_COOKIE;
27031
27285
  if (__ENV.ATC_TOKEN) headers.Authorization = "Bearer " + f(__atcFreshToken());
27032
27286
  if (__ENV.ATC_BASIC_USER !== undefined) headers.Authorization = "Basic " + encoding.b64encode(f(__ENV.ATC_BASIC_USER) + ":" + f(__ENV.ATC_BASIC_PASS || ""));
27033
27287
  if (__ENV.ATC_API_NAME && __ENV.ATC_API_KEY) headers[f(__ENV.ATC_API_NAME)] = f(__ENV.ATC_API_KEY);
27034
27288
  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
27289
  const body = __ENV.ATC_BODY ? f(__ENV.ATC_BODY, __ENV.ATC_BODY_FILL) : null;
27036
27290
  let target = f(__ENV.ATC_URL, "url");
27291
+ if (__ENV.ATC_OAUTH1) target = __atcOAuth1(__ENV.ATC_METHOD, target, headers, body);
27037
27292
  if (__ENV.ATC_AWS) __atcSign(__ENV.ATC_METHOD, target, headers, body);
27038
27293
  if (__ENV.ATC_AUTH_KIND) target = target.replace("://", "://" + encodeURIComponent(f(__ENV.ATC_AUTH_USER)) + ":" + encodeURIComponent(f(__ENV.ATC_AUTH_PASS || "")) + "@");
27039
27294
  const res = http.request(__ENV.ATC_METHOD, target, body, {
@@ -27058,17 +27313,18 @@ export default function () {
27058
27313
  `;
27059
27314
  return { script, env, files };
27060
27315
  }
27061
- function loadDurationHintMs(load2) {
27062
- if (load2.stages.length > 0) {
27063
- return load2.stages.reduce((sum, stage) => sum + durationToMs(stage.duration), 0);
27316
+ function loadDurationHintMs(load3) {
27317
+ if (load3.stages.length > 0) {
27318
+ return load3.stages.reduce((sum, stage) => sum + durationToMs(stage.duration), 0);
27064
27319
  }
27065
- return durationToMs(load2.duration);
27320
+ return durationToMs(load3.duration);
27066
27321
  }
27067
27322
  var init_k6_script = __esm({
27068
27323
  "../shared/src/k6-script.ts"() {
27069
27324
  "use strict";
27070
27325
  init_chain();
27071
27326
  init_checks3();
27327
+ init_oauth1();
27072
27328
  init_sigv4();
27073
27329
  init_definition();
27074
27330
  }
@@ -44903,10 +45159,12 @@ var require_lib = __commonJS({
44903
45159
  import { randomUUID as randomUUID2 } from "node:crypto";
44904
45160
  import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
44905
45161
  import { join as join4 } from "node:path";
44906
- import { createSecureContext } from "node:tls";
44907
- function useCertificateStore(dataDir, store2) {
45162
+ import { X509Certificate } from "node:crypto";
45163
+ import * as tlsModule from "node:tls";
45164
+ import { connect as tlsSocket, createSecureContext, rootCertificates } from "node:tls";
45165
+ function useCertificateStore(dataDir, store3) {
44908
45166
  dir = dataDir;
44909
- secrets = store2;
45167
+ secrets = store3;
44910
45168
  }
44911
45169
  function addTemporaryCertificate(host2, material) {
44912
45170
  checkMaterial(material);
@@ -44928,23 +45186,104 @@ function hostMatches(pattern, url2) {
44928
45186
  }
44929
45187
  return name === host2;
44930
45188
  }
44931
- async function readAll() {
44932
- if (!dir) return [];
45189
+ async function readStore() {
45190
+ if (!dir) return { certificates: [], cas: [] };
44933
45191
  try {
44934
45192
  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") : [];
45193
+ return {
45194
+ certificates: Array.isArray(raw.certificates) ? raw.certificates.filter((c) => c && typeof c.id === "string" && typeof c.host === "string") : [],
45195
+ cas: Array.isArray(raw.cas) ? raw.cas.filter((c) => c && typeof c.id === "string" && typeof c.pem === "string") : []
45196
+ };
44936
45197
  } catch {
44937
- return [];
45198
+ return { certificates: [], cas: [] };
44938
45199
  }
44939
45200
  }
44940
- async function writeAll(list) {
45201
+ async function readAll() {
45202
+ return (await readStore()).certificates;
45203
+ }
45204
+ async function writeStore(next) {
44941
45205
  await mkdir2(dir, { recursive: true });
44942
45206
  const file2 = join4(dir, "certificates.json");
44943
45207
  const tmp = `${file2}.${process.pid}.tmp`;
44944
- await writeFile2(tmp, `${JSON.stringify({ version: 1, certificates: list }, null, 2)}
45208
+ await writeFile2(tmp, `${JSON.stringify({ version: 1, ...next }, null, 2)}
44945
45209
  `, "utf8");
44946
45210
  await rename2(tmp, file2);
44947
45211
  }
45212
+ async function writeAll(list) {
45213
+ await writeStore({ ...await readStore(), certificates: list });
45214
+ }
45215
+ async function listCas() {
45216
+ return (await readStore()).cas.map(({ pem: _pem, ...rest }) => rest);
45217
+ }
45218
+ async function addCas(pemText) {
45219
+ const blocks = pemText.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) ?? [];
45220
+ if (!blocks.length) throw new Error("No certificate in that file \u2014 it should contain -----BEGIN CERTIFICATE-----");
45221
+ const store3 = await readStore();
45222
+ for (const pem of blocks) {
45223
+ let x;
45224
+ try {
45225
+ x = new X509Certificate(pem);
45226
+ } catch {
45227
+ throw new Error("A certificate in that file can't be read");
45228
+ }
45229
+ 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`);
45230
+ if (store3.cas.some((c) => c.pem.replace(/\s/g, "") === pem.replace(/\s/g, ""))) continue;
45231
+ const cn = x.subject.split("\n").find((l) => l.startsWith("CN="))?.slice(3) ?? x.subject.replace(/\n/g, ", ");
45232
+ store3.cas.push({ id: randomUUID2(), subject: cn, expires: new Date(x.validTo).toISOString(), pem });
45233
+ }
45234
+ await writeStore(store3);
45235
+ casCache = null;
45236
+ return listCas();
45237
+ }
45238
+ async function removeCa(id) {
45239
+ const store3 = await readStore();
45240
+ if (!store3.cas.some((c) => c.id === id)) return false;
45241
+ await writeStore({ ...store3, cas: store3.cas.filter((c) => c.id !== id) });
45242
+ casCache = null;
45243
+ return true;
45244
+ }
45245
+ async function trustedCas() {
45246
+ casCache ??= (await readStore()).cas;
45247
+ return casCache.map((c) => c.pem);
45248
+ }
45249
+ function systemCas() {
45250
+ if (systemCache) return systemCache;
45251
+ const get = tlsModule.getCACertificates;
45252
+ try {
45253
+ systemCache = get ? get("system") : [];
45254
+ } catch {
45255
+ systemCache = [];
45256
+ }
45257
+ return systemCache;
45258
+ }
45259
+ async function trustPool() {
45260
+ const added = await trustedCas();
45261
+ const system = systemCas();
45262
+ return added.length || system.length ? [...rootCertificates, ...system, ...added] : null;
45263
+ }
45264
+ async function needsTrustedCa(target) {
45265
+ const extra2 = await trustedCas();
45266
+ let url2;
45267
+ try {
45268
+ url2 = new URL(target);
45269
+ } catch {
45270
+ return false;
45271
+ }
45272
+ if (!extra2.length || url2.protocol !== "https:") return false;
45273
+ const ok = (ca) => new Promise((resolve5) => {
45274
+ const sock = tlsSocket({ host: url2.hostname, port: Number(url2.port || 443), servername: url2.hostname, ca, timeout: 8e3, rejectUnauthorized: false }, () => {
45275
+ resolve5(sock.authorized);
45276
+ sock.end();
45277
+ });
45278
+ sock.on("error", () => resolve5(false));
45279
+ sock.on("timeout", () => {
45280
+ sock.destroy();
45281
+ resolve5(false);
45282
+ });
45283
+ });
45284
+ const system = systemCas();
45285
+ return !await ok(system.length ? [...rootCertificates, ...system] : void 0) && await ok([...rootCertificates, ...system, ...extra2]);
45286
+ }
44948
45287
  async function listCertificates() {
44949
45288
  return (await readAll()).map(({ cert: _cert, ...summary }) => summary);
44950
45289
  }
@@ -45049,8 +45388,18 @@ async function k6TlsAuth(urls) {
45049
45388
  }
45050
45389
  async function tlsConnect(target, skipVerify = false) {
45051
45390
  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 } : {} };
45391
+ const pool = await trustPool();
45392
+ if (!m && !skipVerify && !pool) return void 0;
45393
+ return {
45394
+ ...skipVerify ? { rejectUnauthorized: false } : {},
45395
+ // Added to the usual roots, not instead of them.
45396
+ ...pool ? { ca: pool } : {},
45397
+ ...m ? { cert: m.cert, key: m.key, pfx: m.pfx, passphrase: m.passphrase } : {}
45398
+ };
45399
+ }
45400
+ async function caBundle() {
45401
+ const cas = await trustedCas();
45402
+ return cas.length ? [...rootCertificates, ...systemCas(), ...cas].join("\n") : null;
45054
45403
  }
45055
45404
  function registerCertificateRoutes(app2) {
45056
45405
  app2.get("/certificates", async () => listCertificates());
@@ -45067,12 +45416,27 @@ function registerCertificateRoutes(app2) {
45067
45416
  "/certificates/:id",
45068
45417
  async (request3, reply) => await deleteCertificate(request3.params.id) ? { removed: 1 } : reply.code(404).send({ error: "certificate not found" })
45069
45418
  );
45419
+ app2.get("/certificates/cas", async () => listCas());
45420
+ app2.get("/certificates/system", async () => ({ count: systemCas().length }));
45421
+ app2.post("/certificates/cas", async (request3, reply) => {
45422
+ const pem = request3.body?.pem;
45423
+ if (typeof pem !== "string" || pem.length > 2e6) return reply.code(400).send({ error: "Send the CA file's text as pem" });
45424
+ try {
45425
+ return await addCas(pem);
45426
+ } catch (err) {
45427
+ return reply.code(422).send({ error: err instanceof Error ? err.message : String(err) });
45428
+ }
45429
+ });
45430
+ app2.delete(
45431
+ "/certificates/cas/:id",
45432
+ async (request3, reply) => await removeCa(request3.params.id) ? { removed: 1 } : reply.code(404).send({ error: "not found" })
45433
+ );
45070
45434
  app2.get("/certificates/match", async (request3) => {
45071
45435
  const m = await certificateFor(request3.query.url ?? "").catch(() => null);
45072
45436
  return { host: m?.host ?? null };
45073
45437
  });
45074
45438
  }
45075
- var import_node_forge, dir, secrets, extra, saveBody;
45439
+ var import_node_forge, dir, secrets, extra, casCache, systemCache, saveBody;
45076
45440
  var init_certificates = __esm({
45077
45441
  "../../apps/engine/src/certificates.ts"() {
45078
45442
  "use strict";
@@ -45081,6 +45445,8 @@ var init_certificates = __esm({
45081
45445
  dir = "";
45082
45446
  secrets = null;
45083
45447
  extra = [];
45448
+ casCache = null;
45449
+ systemCache = null;
45084
45450
  saveBody = external_exports.object({
45085
45451
  id: external_exports.string().optional(),
45086
45452
  host: external_exports.string().min(1).max(253),
@@ -45094,6 +45460,113 @@ var init_certificates = __esm({
45094
45460
  }
45095
45461
  });
45096
45462
 
45463
+ // ../../apps/engine/src/cookies.ts
45464
+ function useCookieStore(secrets2) {
45465
+ store = secrets2;
45466
+ jar = null;
45467
+ }
45468
+ async function load() {
45469
+ if (jar) return jar;
45470
+ try {
45471
+ const raw = store ? await store.get(KEY) : null;
45472
+ jar = raw ? JSON.parse(raw).filter((c) => c.expires !== null) : [];
45473
+ } catch {
45474
+ jar = [];
45475
+ }
45476
+ return jar;
45477
+ }
45478
+ async function save(next) {
45479
+ const now = Date.now();
45480
+ jar = next.filter((c) => c.expires === null || c.expires > now).slice(-MAX);
45481
+ await store?.set(KEY, JSON.stringify(jar));
45482
+ }
45483
+ function defaultPath(url2) {
45484
+ const p = url2.pathname;
45485
+ if (!p.startsWith("/") || p.lastIndexOf("/") === 0) return "/";
45486
+ return p.slice(0, p.lastIndexOf("/"));
45487
+ }
45488
+ function parseSetCookie(header, url2, now = Date.now()) {
45489
+ const [pair, ...attrs] = header.split(";");
45490
+ const eq = pair.indexOf("=");
45491
+ if (eq <= 0) return null;
45492
+ const host2 = url2.hostname.toLowerCase();
45493
+ 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 };
45494
+ let maxAge = null;
45495
+ for (const raw of attrs) {
45496
+ const i = raw.indexOf("=");
45497
+ const k = (i < 0 ? raw : raw.slice(0, i)).trim().toLowerCase();
45498
+ const v = i < 0 ? "" : raw.slice(i + 1).trim();
45499
+ if (k === "domain" && v) {
45500
+ const d = v.replace(/^\./, "").toLowerCase();
45501
+ if (host2 !== d && !host2.endsWith(`.${d}`)) return null;
45502
+ cookie.domain = d;
45503
+ cookie.includeSubdomains = true;
45504
+ } else if (k === "path" && v.startsWith("/")) cookie.path = v;
45505
+ else if (k === "expires") {
45506
+ const t = Date.parse(v);
45507
+ if (Number.isFinite(t)) cookie.expires = t;
45508
+ } else if (k === "max-age" && /^-?\d+$/.test(v)) maxAge = Number(v);
45509
+ else if (k === "secure") cookie.secure = true;
45510
+ else if (k === "httponly") cookie.httpOnly = true;
45511
+ else if (k === "samesite") cookie.sameSite = v;
45512
+ }
45513
+ if (maxAge !== null) cookie.expires = now + maxAge * 1e3;
45514
+ return cookie;
45515
+ }
45516
+ function matches(c, url2, now) {
45517
+ const host2 = url2.hostname.toLowerCase();
45518
+ const domainOk = c.includeSubdomains ? host2 === c.domain || host2.endsWith(`.${c.domain}`) : host2 === c.domain;
45519
+ const path = url2.pathname || "/";
45520
+ const pathOk = path === c.path || path.startsWith(c.path) && (c.path.endsWith("/") || path[c.path.length] === "/");
45521
+ return domainOk && pathOk && (!c.secure || url2.protocol === "https:") && (c.expires === null || c.expires > now);
45522
+ }
45523
+ async function cookieHeader(target) {
45524
+ let url2;
45525
+ try {
45526
+ url2 = typeof target === "string" ? new URL(target) : target;
45527
+ } catch {
45528
+ return "";
45529
+ }
45530
+ const now = Date.now();
45531
+ 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("; ");
45532
+ }
45533
+ async function storeCookies(target, setCookie) {
45534
+ if (!setCookie || Array.isArray(setCookie) && !setCookie.length) return;
45535
+ const url2 = typeof target === "string" ? new URL(target) : target;
45536
+ const now = Date.now();
45537
+ let next = [...await load()];
45538
+ for (const header of Array.isArray(setCookie) ? setCookie : [setCookie]) {
45539
+ const c = parseSetCookie(header, url2, now);
45540
+ if (!c) continue;
45541
+ next = next.filter((o) => !(o.name === c.name && o.domain === c.domain && o.path === c.path));
45542
+ if (c.expires === null || c.expires > now) next.push(c);
45543
+ }
45544
+ await save(next);
45545
+ }
45546
+ async function listCookies() {
45547
+ const now = Date.now();
45548
+ return (await load()).filter((c) => c.expires === null || c.expires > now);
45549
+ }
45550
+ function registerCookieRoutes(app2) {
45551
+ 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 })));
45552
+ app2.post("/cookies/delete", async (request3) => {
45553
+ const b = request3.body ?? {};
45554
+ const list = await load();
45555
+ 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)));
45556
+ return { left: (await listCookies()).length };
45557
+ });
45558
+ }
45559
+ var KEY, MAX, store, jar;
45560
+ var init_cookies = __esm({
45561
+ "../../apps/engine/src/cookies.ts"() {
45562
+ "use strict";
45563
+ KEY = "cookies";
45564
+ MAX = 500;
45565
+ store = null;
45566
+ jar = null;
45567
+ }
45568
+ });
45569
+
45097
45570
  // ../../apps/engine/src/jwt.ts
45098
45571
  import { createHmac, createPrivateKey, sign as cryptoSign, constants as constants2 } from "node:crypto";
45099
45572
  function privateKey(pem) {
@@ -45103,7 +45576,7 @@ function privateKey(pem) {
45103
45576
  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
45577
  }
45105
45578
  }
45106
- function signJwt(c, now = Math.floor(Date.now() / 1e3)) {
45579
+ function signJwt(c, now = Math.floor(Date.now() / 1e3), headerExtra = {}) {
45107
45580
  let claims;
45108
45581
  try {
45109
45582
  claims = JSON.parse(c.payload || "{}");
@@ -45113,6 +45586,7 @@ function signJwt(c, now = Math.floor(Date.now() / 1e3)) {
45113
45586
  const body = { iat: now, exp: now + c.lifetimeSec, ...claims };
45114
45587
  const header = { alg: c.algorithm, typ: "JWT" };
45115
45588
  if (c.kid) header.kid = c.kid;
45589
+ Object.assign(header, headerExtra);
45116
45590
  const input = `${b64url(JSON.stringify(header))}.${b64url(JSON.stringify(body))}`;
45117
45591
  const bits = c.algorithm.slice(2);
45118
45592
  const hash2 = `sha${bits}`;
@@ -59388,13 +59862,13 @@ var require_mock_call_history = __commonJS({
59388
59862
  "use strict";
59389
59863
  var { kMockCallHistoryAddLog } = require_mock_symbols();
59390
59864
  var { InvalidArgumentError } = require_errors2();
59391
- function handleFilterCallsWithOptions(criteria, options, handler, store2, allLogs) {
59865
+ function handleFilterCallsWithOptions(criteria, options, handler, store3, allLogs) {
59392
59866
  switch (options.operator) {
59393
59867
  case "OR":
59394
- store2.push(...handler(criteria, allLogs));
59395
- return store2;
59868
+ store3.push(...handler(criteria, allLogs));
59869
+ return store3;
59396
59870
  case "AND":
59397
- return handler(criteria, store2);
59871
+ return handler(criteria, store3);
59398
59872
  default:
59399
59873
  throw new InvalidArgumentError("options.operator must to be a case insensitive string equal to 'OR' or 'AND'");
59400
59874
  }
@@ -62049,12 +62523,12 @@ var require_cache = __commonJS({
62049
62523
  }
62050
62524
  return false;
62051
62525
  }
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}`);
62526
+ function assertCacheStore(store3, name = "CacheStore") {
62527
+ if (typeof store3 !== "object" || store3 === null) {
62528
+ throw new TypeError(`expected type of ${name} to be a CacheStore, got ${store3 === null ? "null" : typeof store3}`);
62055
62529
  }
62056
62530
  for (const fn of ["get", "createWriteStream", "delete"]) {
62057
- if (typeof store2[fn] !== "function") {
62531
+ if (typeof store3[fn] !== "function") {
62058
62532
  throw new TypeError(`${name} needs to have a \`${fn}()\` function`);
62059
62533
  }
62060
62534
  }
@@ -62677,15 +63151,15 @@ var require_cache_handler = __commonJS({
62677
63151
  }
62678
63152
  return locationUrl.pathname + locationUrl.search;
62679
63153
  }
62680
- function deleteCachedUri(store2, cacheKey, path) {
62681
- deleteCachedValue(store2, {
63154
+ function deleteCachedUri(store3, cacheKey, path) {
63155
+ deleteCachedValue(store3, {
62682
63156
  ...cacheKey,
62683
63157
  path
62684
63158
  });
62685
63159
  for (let i = 0; i < util.safeHTTPMethods.length; i++) {
62686
63160
  const method = util.safeHTTPMethods[i];
62687
63161
  if (method !== cacheKey.method) {
62688
- deleteCachedValue(store2, {
63162
+ deleteCachedValue(store3, {
62689
63163
  ...cacheKey,
62690
63164
  method,
62691
63165
  path
@@ -62693,7 +63167,7 @@ var require_cache_handler = __commonJS({
62693
63167
  }
62694
63168
  }
62695
63169
  }
62696
- function deleteLocationTargets(store2, cacheKey, headerValue) {
63170
+ function deleteLocationTargets(store3, cacheKey, headerValue) {
62697
63171
  if (headerValue === void 0) {
62698
63172
  return;
62699
63173
  }
@@ -62701,14 +63175,14 @@ var require_cache_handler = __commonJS({
62701
63175
  for (let i = 0; i < values.length; i++) {
62702
63176
  const path = getSameOriginPath(cacheKey, values[i]);
62703
63177
  if (path !== void 0) {
62704
- deleteCachedUri(store2, cacheKey, path);
63178
+ deleteCachedUri(store3, cacheKey, path);
62705
63179
  }
62706
63180
  }
62707
63181
  }
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"]);
63182
+ function invalidateUnsafeRequest(store3, cacheKey, resHeaders) {
63183
+ deleteCachedUri(store3, cacheKey, cacheKey.path);
63184
+ deleteLocationTargets(store3, cacheKey, resHeaders.location);
63185
+ deleteLocationTargets(store3, cacheKey, resHeaders["content-location"]);
62712
63186
  }
62713
63187
  var CacheHandler = class {
62714
63188
  /**
@@ -62740,8 +63214,8 @@ var require_cache_handler = __commonJS({
62740
63214
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
62741
63215
  * @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
62742
63216
  */
62743
- constructor({ store: store2, type, cacheByDefault }, cacheKey, handler) {
62744
- this.#store = store2;
63217
+ constructor({ store: store3, type, cacheByDefault }, cacheKey, handler) {
63218
+ this.#store = store3;
62745
63219
  this.#cacheType = type;
62746
63220
  this.#cacheByDefault = cacheByDefault;
62747
63221
  this.#cacheKey = cacheKey;
@@ -62935,15 +63409,15 @@ var require_cache_handler = __commonJS({
62935
63409
  this.#handler.onResponseError?.(controller, err);
62936
63410
  }
62937
63411
  };
62938
- function deleteCachedValue(store2, cacheKey) {
63412
+ function deleteCachedValue(store3, cacheKey) {
62939
63413
  try {
62940
- store2.delete(cacheKey)?.catch?.(noop);
63414
+ store3.delete(cacheKey)?.catch?.(noop);
62941
63415
  } catch {
62942
63416
  }
62943
63417
  }
62944
- function deleteCachedValueIfNotModified(statusCode, store2, cacheKey) {
63418
+ function deleteCachedValueIfNotModified(statusCode, store3, cacheKey) {
62945
63419
  if (statusCode === 304) {
62946
- deleteCachedValue(store2, cacheKey);
63420
+ deleteCachedValue(store3, cacheKey);
62947
63421
  }
62948
63422
  }
62949
63423
  function revalidationResponseDisallowsCachedReuse(cacheType, resHeaders, cacheControlDirectives) {
@@ -63226,7 +63700,7 @@ var require_memory_cache_store = __commonJS({
63226
63700
  assertCacheKey(key);
63227
63701
  assertCacheValue(val);
63228
63702
  const topLevelKey = `${key.origin}:${key.path}`;
63229
- const store2 = this;
63703
+ const store3 = this;
63230
63704
  const entry = { ...key, ...val, body: [], size: 0 };
63231
63705
  return new Writable({
63232
63706
  write(chunk, encoding, callback) {
@@ -63234,7 +63708,7 @@ var require_memory_cache_store = __commonJS({
63234
63708
  chunk = Buffer.from(chunk, encoding);
63235
63709
  }
63236
63710
  entry.size += chunk.byteLength;
63237
- if (entry.size > store2.#maxEntrySize) {
63711
+ if (entry.size > store3.#maxEntrySize) {
63238
63712
  this.destroy();
63239
63713
  } else {
63240
63714
  entry.body.push(chunk);
@@ -63242,42 +63716,42 @@ var require_memory_cache_store = __commonJS({
63242
63716
  callback(null);
63243
63717
  },
63244
63718
  final(callback) {
63245
- let entries = store2.#entries.get(topLevelKey);
63719
+ let entries = store3.#entries.get(topLevelKey);
63246
63720
  if (!entries) {
63247
63721
  entries = [];
63248
- store2.#entries.set(topLevelKey, entries);
63722
+ store3.#entries.set(topLevelKey, entries);
63249
63723
  }
63250
63724
  const previousEntry = findEntry(key, entries, Date.now());
63251
63725
  if (previousEntry) {
63252
63726
  const index = entries.indexOf(previousEntry);
63253
63727
  entries.splice(index, 1, entry);
63254
- store2.#size -= previousEntry.size;
63728
+ store3.#size -= previousEntry.size;
63255
63729
  } else {
63256
63730
  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
63731
+ store3.#count += 1;
63732
+ }
63733
+ store3.#size += entry.size;
63734
+ if (store3.#size > store3.#maxSize || store3.#count > store3.#maxCount) {
63735
+ if (!store3.#hasEmittedMaxSizeEvent) {
63736
+ store3.emit("maxSizeExceeded", {
63737
+ size: store3.#size,
63738
+ maxSize: store3.#maxSize,
63739
+ count: store3.#count,
63740
+ maxCount: store3.#maxCount
63267
63741
  });
63268
- store2.#hasEmittedMaxSizeEvent = true;
63742
+ store3.#hasEmittedMaxSizeEvent = true;
63269
63743
  }
63270
- for (const [key2, entries2] of store2.#entries) {
63744
+ for (const [key2, entries2] of store3.#entries) {
63271
63745
  for (const entry2 of entries2.splice(0, entries2.length / 2)) {
63272
- store2.#size -= entry2.size;
63273
- store2.#count -= 1;
63746
+ store3.#size -= entry2.size;
63747
+ store3.#count -= 1;
63274
63748
  }
63275
63749
  if (entries2.length === 0) {
63276
- store2.#entries.delete(key2);
63750
+ store3.#entries.delete(key2);
63277
63751
  }
63278
63752
  }
63279
- if (store2.#size < store2.#maxSize && store2.#count < store2.#maxCount) {
63280
- store2.#hasEmittedMaxSizeEvent = false;
63753
+ if (store3.#size < store3.#maxSize && store3.#count < store3.#maxCount) {
63754
+ store3.#hasEmittedMaxSizeEvent = false;
63281
63755
  }
63282
63756
  }
63283
63757
  callback(null);
@@ -63524,9 +63998,9 @@ var require_cache2 = __commonJS({
63524
63998
  function revalidationResponseUpdatesCacheControl(headers) {
63525
63999
  return headers["cache-control"] !== void 0;
63526
64000
  }
63527
- function deleteCachedValue(store2, cacheKey) {
64001
+ function deleteCachedValue(store3, cacheKey) {
63528
64002
  try {
63529
- store2.delete(cacheKey)?.catch?.(nop);
64003
+ store3.delete(cacheKey)?.catch?.(nop);
63530
64004
  } catch {
63531
64005
  }
63532
64006
  }
@@ -63761,7 +64235,7 @@ var require_cache2 = __commonJS({
63761
64235
  }
63762
64236
  module.exports = (opts = {}) => {
63763
64237
  const {
63764
- store: store2 = new MemoryCacheStore(),
64238
+ store: store3 = new MemoryCacheStore(),
63765
64239
  methods = ["GET"],
63766
64240
  cacheByDefault = void 0,
63767
64241
  type = "shared",
@@ -63770,7 +64244,7 @@ var require_cache2 = __commonJS({
63770
64244
  if (typeof opts !== "object" || opts === null) {
63771
64245
  throw new TypeError(`expected type of opts to be an Object, got ${opts === null ? "null" : typeof opts}`);
63772
64246
  }
63773
- assertCacheStore(store2, "opts.store");
64247
+ assertCacheStore(store3, "opts.store");
63774
64248
  assertCacheMethods(methods, "opts.methods");
63775
64249
  assertCacheOrigins(origins, "opts.origins");
63776
64250
  if (typeof cacheByDefault !== "undefined" && typeof cacheByDefault !== "number") {
@@ -63780,7 +64254,7 @@ var require_cache2 = __commonJS({
63780
64254
  throw new TypeError(`expected opts.type to be shared, private, or undefined, got ${typeof type}`);
63781
64255
  }
63782
64256
  const globalOpts = {
63783
- store: store2,
64257
+ store: store3,
63784
64258
  methods,
63785
64259
  cacheByDefault,
63786
64260
  type
@@ -63828,7 +64302,7 @@ var require_cache2 = __commonJS({
63828
64302
  return dispatch(opts2, handler);
63829
64303
  }
63830
64304
  const cacheKey = makeCacheKey(opts2);
63831
- const result = store2.get(cacheKey);
64305
+ const result = store3.get(cacheKey);
63832
64306
  if (result && typeof result.then === "function") {
63833
64307
  return result.then((result2) => handleResult(
63834
64308
  dispatch,
@@ -64791,12 +65265,12 @@ var require_sqlite_cache_store = __commonJS({
64791
65265
  assertCacheValue(value);
64792
65266
  let size = 0;
64793
65267
  const body = [];
64794
- const store2 = this;
65268
+ const store3 = this;
64795
65269
  return new Writable({
64796
65270
  decodeStrings: true,
64797
65271
  write(chunk, encoding, callback) {
64798
65272
  size += chunk.byteLength;
64799
- if (size <= store2.#maxEntrySize) {
65273
+ if (size <= store3.#maxEntrySize) {
64800
65274
  body.push(chunk);
64801
65275
  } else {
64802
65276
  this.destroy();
@@ -64804,7 +65278,7 @@ var require_sqlite_cache_store = __commonJS({
64804
65278
  callback();
64805
65279
  },
64806
65280
  final(callback) {
64807
- store2.set(key, { ...value, body });
65281
+ store3.set(key, { ...value, body });
64808
65282
  callback();
64809
65283
  }
64810
65284
  });
@@ -64868,17 +65342,17 @@ var require_sqlite_cache_store = __commonJS({
64868
65342
  if (now >= value.deleteAt && !canBeExpired) {
64869
65343
  continue;
64870
65344
  }
64871
- let matches = true;
65345
+ let matches2 = true;
64872
65346
  if (value.vary) {
64873
65347
  const vary = JSON.parse(value.vary);
64874
65348
  for (const header in vary) {
64875
65349
  if (!headerValueEquals(headers[header], vary[header])) {
64876
- matches = false;
65350
+ matches2 = false;
64877
65351
  break;
64878
65352
  }
64879
65353
  }
64880
65354
  }
64881
- if (matches) {
65355
+ if (matches2) {
64882
65356
  return value;
64883
65357
  }
64884
65358
  }
@@ -68771,7 +69245,7 @@ var require_parse = __commonJS({
68771
69245
  var { maxNameValuePairSize, maxAttributeValueSize } = require_constants4();
68772
69246
  var { isCTLExcludingHtab } = require_util5();
68773
69247
  var assert2 = __require("node:assert");
68774
- function parseSetCookie(header) {
69248
+ function parseSetCookie2(header) {
68775
69249
  if (isCTLExcludingHtab(header)) {
68776
69250
  return null;
68777
69251
  }
@@ -68895,7 +69369,7 @@ var require_parse = __commonJS({
68895
69369
  return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList);
68896
69370
  }
68897
69371
  module.exports = {
68898
- parseSetCookie,
69372
+ parseSetCookie: parseSetCookie2,
68899
69373
  parseUnparsedAttributes
68900
69374
  };
68901
69375
  }
@@ -68905,7 +69379,7 @@ var require_parse = __commonJS({
68905
69379
  var require_cookies = __commonJS({
68906
69380
  "../../node_modules/.pnpm/undici@8.10.0/node_modules/undici/lib/web/cookies/index.js"(exports, module) {
68907
69381
  "use strict";
68908
- var { parseSetCookie } = require_parse();
69382
+ var { parseSetCookie: parseSetCookie2 } = require_parse();
68909
69383
  var { stringify } = require_util5();
68910
69384
  var { webidl } = require_webidl();
68911
69385
  var { Headers } = require_headers();
@@ -68944,11 +69418,11 @@ var require_cookies = __commonJS({
68944
69418
  if (!cookies) {
68945
69419
  return [];
68946
69420
  }
68947
- return cookies.map((pair) => parseSetCookie(pair));
69421
+ return cookies.map((pair) => parseSetCookie2(pair));
68948
69422
  }
68949
69423
  function parseCookie(cookie) {
68950
69424
  cookie = webidl.converters.DOMString(cookie);
68951
- return parseSetCookie(cookie);
69425
+ return parseSetCookie2(cookie);
68952
69426
  }
68953
69427
  function setCookie(headers, cookie) {
68954
69428
  webidl.argumentLengthCheck(arguments, 2, "setCookie");
@@ -72104,19 +72578,19 @@ ${captureLines}` : capture.stack;
72104
72578
  });
72105
72579
 
72106
72580
  // ../../apps/engine/src/oauth.ts
72107
- import { createHash as createHash2, randomBytes, randomUUID as randomUUID3 } from "node:crypto";
72581
+ import { X509Certificate as X509Certificate2, createHash as createHash2, randomBytes, randomUUID as randomUUID3 } from "node:crypto";
72108
72582
  import { createServer } from "node:http";
72109
72583
  function useOAuthStore(secrets2) {
72110
- store = secrets2;
72584
+ store2 = secrets2;
72111
72585
  }
72112
72586
  function oauthKey(c) {
72113
72587
  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
72588
  return createHash2("sha256").update(id).digest("hex").slice(0, 32);
72115
72589
  }
72116
- async function load(key) {
72590
+ async function load2(key) {
72117
72591
  const hit = memory.get(key);
72118
72592
  if (hit) return hit;
72119
- const raw = await store?.get(`oauth:${key}`);
72593
+ const raw = await store2?.get(`oauth:${key}`);
72120
72594
  if (!raw) return null;
72121
72595
  try {
72122
72596
  const token = JSON.parse(raw);
@@ -72126,21 +72600,54 @@ async function load(key) {
72126
72600
  return null;
72127
72601
  }
72128
72602
  }
72129
- async function save(key, token) {
72603
+ async function save2(key, token) {
72130
72604
  memory.set(key, token);
72131
- await store?.set(`oauth:${key}`, JSON.stringify(token));
72605
+ await store2?.set(`oauth:${key}`, JSON.stringify(token));
72132
72606
  }
72133
72607
  async function clearToken(c) {
72134
72608
  const key = oauthKey(c);
72135
72609
  memory.delete(key);
72136
- await store?.set(`oauth:${key}`, "");
72610
+ await store2?.set(`oauth:${key}`, "");
72137
72611
  }
72138
- function tokenRequest(c, fields) {
72612
+ function thumbprint(pem) {
72613
+ if (!pem.trim()) return void 0;
72614
+ try {
72615
+ return Buffer.from(new X509Certificate2(pem).fingerprint.replace(/:/g, ""), "hex").toString("base64url");
72616
+ } catch {
72617
+ throw new OAuthError("the certificate can't be read \u2014 paste the whole PEM, from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE-----");
72618
+ }
72619
+ }
72620
+ function signedAssertion(c, claims, now) {
72621
+ const x5t = thumbprint(c.certificate);
72622
+ try {
72623
+ return signJwt(
72624
+ { algorithm: c.assertionAlgorithm, key: c.privateKey, payload: JSON.stringify({ jti: randomUUID3(), ...claims }), lifetimeSec: 300, kid: c.kid, headerName: "", prefix: "" },
72625
+ now,
72626
+ x5t ? { x5t } : {}
72627
+ );
72628
+ } catch (err) {
72629
+ throw new OAuthError(err instanceof Error ? err.message : String(err));
72630
+ }
72631
+ }
72632
+ function extraClaims(c) {
72633
+ if (!c.extraClaims) return {};
72634
+ try {
72635
+ return JSON.parse(c.extraClaims);
72636
+ } catch {
72637
+ throw new OAuthError("the extra claims are not valid JSON");
72638
+ }
72639
+ }
72640
+ function tokenRequest(c, fields, now = Math.floor(Date.now() / 1e3)) {
72139
72641
  const form = new URLSearchParams(fields);
72140
72642
  if (c.scope && !form.has("scope") && fields.grant_type !== "authorization_code") form.set("scope", c.scope);
72141
72643
  for (const e of c.extra) if (e.key.trim()) form.set(e.key.trim(), e.value);
72142
72644
  const headers = { "content-type": "application/x-www-form-urlencoded", accept: "application/json" };
72143
- if (c.clientAuth === "basic" && c.clientSecret) {
72645
+ if (c.clientAuth === "private_key_jwt") {
72646
+ form.set("client_id", c.clientId);
72647
+ form.set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
72648
+ form.set("client_assertion", signedAssertion(c, { iss: c.clientId, sub: c.clientId, aud: c.audience || c.tokenUrl }, now));
72649
+ } else if (fields.grant_type === JWT_BEARER && !c.clientSecret) {
72650
+ } else if (c.clientAuth === "basic" && c.clientSecret) {
72144
72651
  headers.authorization = `Basic ${Buffer.from(`${encodeURIComponent(c.clientId)}:${encodeURIComponent(c.clientSecret)}`).toString("base64")}`;
72145
72652
  } else {
72146
72653
  form.set("client_id", c.clientId);
@@ -72182,39 +72689,84 @@ async function postToken(c, fields) {
72182
72689
  obtainedAt: Date.now()
72183
72690
  };
72184
72691
  }
72185
- function grantFields(c) {
72692
+ async function grantFields(c, now = Math.floor(Date.now() / 1e3)) {
72186
72693
  if (c.grant === "client_credentials") return { grant_type: "client_credentials" };
72187
72694
  if (c.grant === "password") return { grant_type: "password", username: c.username, password: c.password };
72695
+ if (c.grant === "jwt_bearer") {
72696
+ const claims = { iss: c.issuer || c.clientId, ...c.subject ? { sub: c.subject } : {}, aud: c.audience || c.tokenUrl, ...extraClaims(c) };
72697
+ return { grant_type: JWT_BEARER, assertion: signedAssertion(c, claims, now) };
72698
+ }
72699
+ if (c.grant === "saml_bearer") {
72700
+ const fields = { grant_type: SAML_BEARER, assertion: c.samlSource === "assertion" ? c.assertion : await samlFromIdp(c) };
72701
+ if (c.companyId) fields.company_id = c.companyId;
72702
+ return fields;
72703
+ }
72188
72704
  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
72705
  }
72706
+ async function samlFromIdp(c) {
72707
+ const key = c.privateKey.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
72708
+ const form = new URLSearchParams({ client_id: c.clientId, user_id: c.userId, token_url: c.tokenUrl, private_key: key });
72709
+ if (c.companyId) form.set("company_id", c.companyId);
72710
+ let status;
72711
+ let text;
72712
+ try {
72713
+ const connect = await tlsConnect(c.idpUrl);
72714
+ 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 });
72715
+ status = res.statusCode;
72716
+ text = (await res.body.text()).trim();
72717
+ } catch (err) {
72718
+ throw new OAuthError(`couldn't reach the assertion URL (${err instanceof Error ? err.message : "network error"})`);
72719
+ }
72720
+ if (status < 200 || status >= 300 || !text || text.startsWith("{") || text.startsWith("<")) {
72721
+ throw new OAuthError(`the assertion URL answered ${status} (${text.slice(0, 160) || "no details"}) \u2014 check the client ID, user ID and private key`);
72722
+ }
72723
+ return text;
72724
+ }
72190
72725
  async function getToken(c, opts = {}) {
72191
72726
  const key = oauthKey(c);
72192
- const kept = await load(key);
72727
+ const kept = await load2(key);
72193
72728
  if (!opts.force && fresh(kept)) return kept;
72194
72729
  if (kept?.refreshToken) {
72195
72730
  try {
72196
72731
  const next = await postToken(c, { grant_type: "refresh_token", refresh_token: kept.refreshToken });
72197
72732
  const token2 = { ...next, refreshToken: next.refreshToken ?? kept.refreshToken };
72198
- await save(key, token2);
72733
+ await save2(key, token2);
72199
72734
  return token2;
72200
72735
  } catch (err) {
72201
72736
  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
72737
  }
72203
72738
  }
72204
- const token = await postToken(c, grantFields(c));
72205
- await save(key, token);
72739
+ const token = await postToken(c, await grantFields(c));
72740
+ await save2(key, token);
72206
72741
  return token;
72207
72742
  }
72208
72743
  async function tokenStatus(c) {
72209
- const t = await load(oauthKey(c));
72744
+ const t = await load2(oauthKey(c));
72210
72745
  if (!t?.accessToken) return { hasToken: false, expiresAt: null, canRefresh: c.grant !== "authorization_code" };
72211
72746
  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
72747
  }
72213
- function refreshPlan(c, t) {
72748
+ async function refreshPlan(c, t, runMs = 36e5) {
72214
72749
  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 };
72750
+ if (c.grant === "authorization_code") {
72751
+ return t.refreshToken ? { tokenUrl: c.tokenUrl, ...tokenRequest(c, { grant_type: "refresh_token", refresh_token: t.refreshToken }), expiresAt: t.expiresAt } : null;
72752
+ }
72753
+ if (c.grant === "saml_bearer" && c.samlSource === "idp") return null;
72754
+ const signs = c.grant === "jwt_bearer" || c.clientAuth === "private_key_jwt";
72755
+ if (!signs) {
72756
+ return { tokenUrl: c.tokenUrl, ...tokenRequest(c, await grantFields(c)), expiresAt: t.expiresAt };
72757
+ }
72758
+ const start = Math.floor(Date.now() / 1e3);
72759
+ const stepSec = 240;
72760
+ const count = Math.min(2e3, Math.ceil((runMs / 1e3 + 120) / stepSec));
72761
+ const list = [];
72762
+ let headers = {};
72763
+ for (let i = 0; i < count; i++) {
72764
+ const now = start + i * stepSec;
72765
+ const req = tokenRequest(c, await grantFields(c, now), now);
72766
+ headers = req.headers;
72767
+ list.push(req.body);
72768
+ }
72769
+ return { tokenUrl: c.tokenUrl, headers, body: list[0], expiresAt: t.expiresAt, bodies: { start, stepSec, list } };
72218
72770
  }
72219
72771
  async function startAuthorization(c) {
72220
72772
  const redirect = new URL(c.redirectUri);
@@ -72257,7 +72809,7 @@ async function startAuthorization(c) {
72257
72809
  if (!code) return finish(false, "No code came back.");
72258
72810
  const fields = { grant_type: "authorization_code", code, redirect_uri: c.redirectUri };
72259
72811
  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."));
72812
+ 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
72813
  });
72262
72814
  await new Promise((resolve5, reject) => {
72263
72815
  server.once(
@@ -72319,14 +72871,15 @@ function registerOAuthRoutes(app2) {
72319
72871
  });
72320
72872
  app2.get("/oauth/authorize/:id", async (request3) => authorizationStatus(request3.params.id));
72321
72873
  }
72322
- var import_undici, store, memory, OAuthError, HINTS, fresh, pending, page, b64url2;
72874
+ var import_undici, store2, memory, OAuthError, HINTS, JWT_BEARER, SAML_BEARER, fresh, pending, page, b64url2;
72323
72875
  var init_oauth = __esm({
72324
72876
  "../../apps/engine/src/oauth.ts"() {
72325
72877
  "use strict";
72326
72878
  init_src();
72327
72879
  import_undici = __toESM(require_undici(), 1);
72328
72880
  init_certificates();
72329
- store = null;
72881
+ init_jwt();
72882
+ store2 = null;
72330
72883
  memory = /* @__PURE__ */ new Map();
72331
72884
  OAuthError = class extends Error {
72332
72885
  };
@@ -72337,6 +72890,8 @@ var init_oauth = __esm({
72337
72890
  invalid_scope: "the scope isn't allowed for this client",
72338
72891
  unsupported_grant_type: "the server doesn't offer this grant type"
72339
72892
  };
72893
+ JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer";
72894
+ SAML_BEARER = "urn:ietf:params:oauth:grant-type:saml2-bearer";
72340
72895
  fresh = (t, marginMs = 6e4) => Boolean(t && (t.expiresAt === null || t.expiresAt - marginMs > Date.now()));
72341
72896
  pending = /* @__PURE__ */ new Map();
72342
72897
  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 +73073,30 @@ var init_aws_sign = __esm({
72518
73073
  }
72519
73074
  });
72520
73075
 
73076
+ // ../../apps/engine/src/oauth1-sign.ts
73077
+ import { createHmac as createHmac4, randomBytes as randomBytes3 } from "node:crypto";
73078
+ function signOAuth1(c, req, nonce = randomBytes3(16).toString("hex"), timestamp = Math.floor(Date.now() / 1e3)) {
73079
+ const oauth = oauth1Params(c, nonce, timestamp);
73080
+ const query = /\?([^#]*)/.exec(req.url)?.[1] ?? "";
73081
+ const params = [...oauth, ...formPairs(query)];
73082
+ if (req.body && /^application\/x-www-form-urlencoded/i.test(req.contentType ?? "")) params.push(...formPairs(req.body));
73083
+ const baseString = oauth1BaseString(req.method, req.url, params);
73084
+ const key = oauth1SigningKey(c);
73085
+ const signature = c.signatureMethod === "PLAINTEXT" ? key : createHmac4(c.signatureMethod === "HMAC-SHA1" ? "sha1" : "sha256", key).update(baseString).digest("base64");
73086
+ const signed = [...oauth, ["oauth_signature", signature]];
73087
+ if (c.addTo === "query") {
73088
+ const extra2 = signed.map(([k, v]) => `${pctEncode(k)}=${pctEncode(v)}`).join("&");
73089
+ return { url: `${req.url}${req.url.includes("?") ? "&" : "?"}${extra2}`, signature, baseString };
73090
+ }
73091
+ return { url: req.url, authorization: oauth1Header(c, signed), signature, baseString };
73092
+ }
73093
+ var init_oauth1_sign = __esm({
73094
+ "../../apps/engine/src/oauth1-sign.ts"() {
73095
+ "use strict";
73096
+ init_src();
73097
+ }
73098
+ });
73099
+
72521
73100
  // ../../apps/engine/src/probe.ts
72522
73101
  function redactHeaders(headers, extraNames = []) {
72523
73102
  const extra2 = new Set(extraNames.map((n) => n.toLowerCase()));
@@ -72617,6 +73196,17 @@ async function runProbe(input) {
72617
73196
  const saved = parsed.save?.length && !sent.result.error ? takeValues(parsed.save, sent) : void 0;
72618
73197
  return { ...sent.result, setup, dataRow: parsed.data ? { row: 1, of: parsed.data.rows.length } : void 0, saved };
72619
73198
  }
73199
+ async function probeFull(input) {
73200
+ const parsed = parseLab(input);
73201
+ let vars = {};
73202
+ if (parsed.setup) {
73203
+ const step = await runSetupStep(parsed.setup);
73204
+ if (!step.result.ok) return { status: 0, body: "", error: step.result.error };
73205
+ vars = step.vars;
73206
+ }
73207
+ const sent = await sendOnce(fillLab(parsed, vars));
73208
+ return { status: sent.result.status, body: sent.raw.slice(0, 2e7), error: sent.result.error };
73209
+ }
72620
73210
  function takeValues(rules, sent) {
72621
73211
  const res = { status: sent.result.status, headers: sent.lowered, body: sent.raw, durationMs: sent.result.durationMs };
72622
73212
  return rules.map((rule) => {
@@ -72714,6 +73304,16 @@ async function sendRaw(lab, extra2 = {}) {
72714
73304
  if (encoded.contentType && !Object.keys(headers).some((k) => k.toLowerCase() === "content-type")) {
72715
73305
  headers["content-type"] = encoded.contentType;
72716
73306
  }
73307
+ let target = url2;
73308
+ if (lab.auth.mode === "oauth1") {
73309
+ const { mode: _mode, ...creds } = lab.auth;
73310
+ const contentType = Object.entries(headers).find(([k]) => k.toLowerCase() === "content-type")?.[1];
73311
+ const signed = signOAuth1(creds, { method: lab.request.method, url: url2.toString(), contentType, body: encoded.body });
73312
+ if (signed.authorization) {
73313
+ headers.authorization = signed.authorization;
73314
+ secretNames.push("authorization");
73315
+ } else target = new URL(signed.url);
73316
+ }
72717
73317
  if (lab.auth.mode === "aws_sigv4") {
72718
73318
  const { mode: _mode, ...creds } = lab.auth;
72719
73319
  Object.assign(headers, signAws(creds, { method: lab.request.method, url: url2.toString(), headers, body: encoded.body }));
@@ -72721,20 +73321,39 @@ async function sendRaw(lab, extra2 = {}) {
72721
73321
  }
72722
73322
  const options = lab.options;
72723
73323
  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 }));
73324
+ const useJar = options.cookies !== false && !extra2.dispatcher;
73325
+ const ownCookie = Object.keys(headers).some((k) => k.toLowerCase() === "cookie");
73326
+ const agent = extra2.dispatcher ?? new import_undici2.Agent({ connect: await tlsConnect(url2, options.tlsSkipVerify) });
73327
+ const dispatcher = extra2.dispatcher ?? (useJar ? agent : agent.compose(import_undici2.interceptors.redirect({ maxRedirections: options.maxRedirects })));
72728
73328
  const started = performance.now();
72729
73329
  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
- });
73330
+ let current = target;
73331
+ let method = lab.request.method;
73332
+ let body = encoded.body;
73333
+ let response;
73334
+ for (let hops = 0; ; hops++) {
73335
+ const sendHeaders = { ...headers };
73336
+ if (useJar && !ownCookie) {
73337
+ const jar2 = await cookieHeader(current);
73338
+ if (jar2) sendHeaders.cookie = jar2;
73339
+ }
73340
+ response = await (0, import_undici2.request)(current, { method, headers: sendHeaders, body, headersTimeout: timeout, bodyTimeout: timeout, dispatcher });
73341
+ if (!useJar) break;
73342
+ await storeCookies(current, response.headers["set-cookie"]);
73343
+ const location = response.headers.location;
73344
+ if (![301, 302, 303, 307, 308].includes(response.statusCode) || typeof location !== "string" || hops >= options.maxRedirects) break;
73345
+ await response.body.dump();
73346
+ const next = new URL(location, current);
73347
+ if (next.origin !== current.origin) {
73348
+ for (const k of Object.keys(headers)) if (k.toLowerCase() === "authorization") delete headers[k];
73349
+ }
73350
+ if (response.statusCode === 303 || (response.statusCode === 301 || response.statusCode === 302) && method === "POST") {
73351
+ method = "GET";
73352
+ body = void 0;
73353
+ for (const k of Object.keys(headers)) if (k.toLowerCase() === "content-type") delete headers[k];
73354
+ }
73355
+ current = next;
73356
+ }
72738
73357
  const raw = await response.body.text();
72739
73358
  const durationMs = Math.round((performance.now() - started) * 10) / 10;
72740
73359
  const truncated = raw.length > BODY_LIMIT;
@@ -72789,8 +73408,10 @@ var init_probe = __esm({
72789
73408
  import_undici2 = __toESM(require_undici(), 1);
72790
73409
  init_challenge_auth();
72791
73410
  init_aws_sign();
73411
+ init_oauth1_sign();
72792
73412
  init_jwt();
72793
73413
  init_certificates();
73414
+ init_cookies();
72794
73415
  init_oauth();
72795
73416
  BODY_LIMIT = 64 * 1024;
72796
73417
  }
@@ -73138,12 +73759,12 @@ function dataDefinition(lab) {
73138
73759
  };
73139
73760
  }
73140
73761
  function loadDefinition(run2) {
73141
- const load2 = run2.lab.load;
73142
- const rate = isArrivalRate(load2.executor);
73762
+ const load3 = run2.lab.load;
73763
+ const rate = isArrivalRate(load3.executor);
73143
73764
  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 }]
73765
+ executor: load3.executor,
73766
+ rate: rate ? load3.rate ?? Math.max(0, ...load3.stages.map((s) => s.target)) : void 0,
73767
+ stages: load3.stages.length > 0 ? load3.stages : [{ duration: load3.duration, target: rate ? load3.rate ?? 0 : load3.vus }]
73147
73768
  };
73148
73769
  }
73149
73770
  function liveSummaryParts(run2) {
@@ -73362,7 +73983,7 @@ async function startRun(lab, repoRoot2) {
73362
73983
  } catch (err) {
73363
73984
  throw Object.assign(new Error(`OAuth 2.0: ${err instanceof Error ? err.message : String(err)}`), { statusCode: 422 });
73364
73985
  }
73365
- refresh = refreshPlan(config2, token);
73986
+ refresh = await refreshPlan(config2, token, loadDurationHintMs(filled.load));
73366
73987
  filled = { ...filled, auth: { mode: "bearer", token: token.accessToken } };
73367
73988
  }
73368
73989
  let tlsAuth;
@@ -73382,7 +74003,19 @@ async function startRun(lab, repoRoot2) {
73382
74003
  }
73383
74004
  filled = { ...filled, auth: { mode: "none" } };
73384
74005
  }
74006
+ const bundle = await caBundle();
74007
+ if (bundle && process.platform !== "linux" && !filled.options.tlsSkipVerify && await needsTrustedCa(filled.request.url)) {
74008
+ filled = { ...filled, options: { ...filled.options, tlsSkipVerify: true } };
74009
+ }
73385
74010
  const generated = generateK6Script(filled, { oauthRefresh: refresh, tlsAuth, jwt: jwt2 });
74011
+ if (filled.options.cookies !== false && !filled.request.headers.some((h) => h.key.toLowerCase() === "cookie")) {
74012
+ const jar2 = await cookieHeader(filled.request.url);
74013
+ if (jar2) generated.env.ATC_COOKIE = jar2;
74014
+ }
74015
+ if (bundle && process.platform === "linux") {
74016
+ await writeFile3(join5(dir2, "ca-bundle.pem"), bundle, "utf8");
74017
+ generated.env.SSL_CERT_FILE = join5(dir2, "ca-bundle.pem");
74018
+ }
73386
74019
  await writeFile3(scriptPath, generated.script, "utf8");
73387
74020
  for (const [name, text] of Object.entries(generated.files)) await writeFile3(join5(dir2, name), text, "utf8");
73388
74021
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -73529,6 +74162,7 @@ var init_load_run = __esm({
73529
74162
  init_k6_script();
73530
74163
  init_src();
73531
74164
  init_certificates();
74165
+ init_cookies();
73532
74166
  init_jwt();
73533
74167
  init_oauth();
73534
74168
  init_probe();
@@ -73542,7 +74176,7 @@ var init_load_run = __esm({
73542
74176
  });
73543
74177
 
73544
74178
  // ../../apps/engine/src/secret-store.ts
73545
- import { createCipheriv, createDecipheriv, randomBytes as randomBytes3 } from "node:crypto";
74179
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes4 } from "node:crypto";
73546
74180
  import { readFile as readFile4, rename as rename3, writeFile as writeFile4 } from "node:fs/promises";
73547
74181
  import { join as join6 } from "node:path";
73548
74182
  var SecretStore;
@@ -73579,7 +74213,7 @@ var init_secret_store = __esm({
73579
74213
  }
73580
74214
  async persist() {
73581
74215
  if (!this.key) return;
73582
- const iv = randomBytes3(12);
74216
+ const iv = randomBytes4(12);
73583
74217
  const cipher = createCipheriv("aes-256-gcm", this.key, iv);
73584
74218
  const body = Buffer.concat([cipher.update(JSON.stringify(Object.fromEntries(this.values)), "utf8"), cipher.final()]);
73585
74219
  const tmp = `${this.file}.${process.pid}.tmp`;
@@ -78624,14 +79258,14 @@ var require_wrap_thenable = __commonJS({
78624
79258
  var { setErrorStatusCode } = require_error_status();
78625
79259
  var diagnostics = __require("node:diagnostics_channel");
78626
79260
  var channels = diagnostics.tracingChannel("fastify.request.handler");
78627
- function wrapThenable(thenable, reply, store2) {
78628
- if (store2) store2.async = true;
79261
+ function wrapThenable(thenable, reply, store3) {
79262
+ if (store3) store3.async = true;
78629
79263
  thenable.then(function(payload) {
78630
79264
  if (reply[kReplyHijacked] === true) {
78631
79265
  return;
78632
79266
  }
78633
- if (store2) {
78634
- channels.asyncStart.publish(store2);
79267
+ if (store3) {
79268
+ channels.asyncStart.publish(store3);
78635
79269
  }
78636
79270
  try {
78637
79271
  if (payload !== void 0 || //
@@ -78645,16 +79279,16 @@ var require_wrap_thenable = __commonJS({
78645
79279
  }
78646
79280
  }
78647
79281
  } finally {
78648
- if (store2) {
78649
- channels.asyncEnd.publish(store2);
79282
+ if (store3) {
79283
+ channels.asyncEnd.publish(store3);
78650
79284
  }
78651
79285
  }
78652
79286
  }, function(err) {
78653
- if (store2) {
78654
- store2.error = err;
79287
+ if (store3) {
79288
+ store3.error = err;
78655
79289
  setErrorStatusCode(reply, err);
78656
- channels.error.publish(store2);
78657
- channels.asyncStart.publish(store2);
79290
+ channels.error.publish(store3);
79291
+ channels.asyncStart.publish(store3);
78658
79292
  }
78659
79293
  try {
78660
79294
  if (reply.sent === true) {
@@ -78666,8 +79300,8 @@ var require_wrap_thenable = __commonJS({
78666
79300
  } catch (err2) {
78667
79301
  reply.send(err2);
78668
79302
  } finally {
78669
- if (store2) {
78670
- channels.asyncEnd.publish(store2);
79303
+ if (store3) {
79304
+ channels.asyncEnd.publish(store3);
78671
79305
  }
78672
79306
  }
78673
79307
  });
@@ -78994,10 +79628,10 @@ var require_content_type = __commonJS({
78994
79628
  this.#subtype = subtype;
78995
79629
  this.#valid = true;
78996
79630
  this.#empty = false;
78997
- let matches = keyValuePairsReg.exec(paramsList);
78998
- while (matches) {
78999
- const key = matches[1].toLowerCase();
79000
- let value = matches[2];
79631
+ let matches2 = keyValuePairsReg.exec(paramsList);
79632
+ while (matches2) {
79633
+ const key = matches2[1].toLowerCase();
79634
+ let value = matches2[2];
79001
79635
  if (value.charCodeAt(0) === 34) {
79002
79636
  value = value.slice(1, -1);
79003
79637
  if (value.indexOf("\\") !== -1) {
@@ -79005,7 +79639,7 @@ var require_content_type = __commonJS({
79005
79639
  }
79006
79640
  }
79007
79641
  this.#parameters.set(key, value);
79008
- matches = keyValuePairsReg.exec(paramsList);
79642
+ matches2 = keyValuePairsReg.exec(paramsList);
79009
79643
  }
79010
79644
  }
79011
79645
  get [Symbol.toStringTag]() {
@@ -79183,7 +79817,7 @@ var require_handle_request = __commonJS({
79183
79817
  if (!channels.hasSubscribers || context[kFourOhFourContext] === null) {
79184
79818
  preHandlerCallbackInner(err, request3, reply);
79185
79819
  } else {
79186
- const store2 = {
79820
+ const store3 = {
79187
79821
  request: request3,
79188
79822
  reply,
79189
79823
  async: false,
@@ -79192,19 +79826,19 @@ var require_handle_request = __commonJS({
79192
79826
  method: context.config.method
79193
79827
  }
79194
79828
  };
79195
- reply[kDiagnosticsStore] = store2;
79196
- channels.start.runStores(store2, preHandlerCallbackInner, void 0, err, request3, reply, store2);
79829
+ reply[kDiagnosticsStore] = store3;
79830
+ channels.start.runStores(store3, preHandlerCallbackInner, void 0, err, request3, reply, store3);
79197
79831
  }
79198
79832
  }
79199
- function preHandlerCallbackInner(err, request3, reply, store2) {
79833
+ function preHandlerCallbackInner(err, request3, reply, store3) {
79200
79834
  const context = request3[kRouteContext];
79201
79835
  try {
79202
79836
  if (err != null) {
79203
79837
  reply[kReplyIsError] = true;
79204
- if (store2) {
79205
- store2.error = err;
79838
+ if (store3) {
79839
+ store3.error = err;
79206
79840
  setErrorStatusCode(reply, err);
79207
- channels.error.publish(store2);
79841
+ channels.error.publish(store3);
79208
79842
  }
79209
79843
  reply.send(err);
79210
79844
  return;
@@ -79213,10 +79847,10 @@ var require_handle_request = __commonJS({
79213
79847
  try {
79214
79848
  result = context.handler(request3, reply);
79215
79849
  } catch (err2) {
79216
- if (store2) {
79217
- store2.error = err2;
79850
+ if (store3) {
79851
+ store3.error = err2;
79218
79852
  setErrorStatusCode(reply, err2);
79219
- channels.error.publish(store2);
79853
+ channels.error.publish(store3);
79220
79854
  }
79221
79855
  reply[kReplyIsError] = true;
79222
79856
  reply.send(err2);
@@ -79224,13 +79858,13 @@ var require_handle_request = __commonJS({
79224
79858
  }
79225
79859
  if (result !== void 0) {
79226
79860
  if (result !== null && typeof result.then === "function") {
79227
- wrapThenable(result, reply, store2);
79861
+ wrapThenable(result, reply, store3);
79228
79862
  } else {
79229
79863
  reply.send(result);
79230
79864
  }
79231
79865
  }
79232
79866
  } finally {
79233
- if (store2) channels.end.publish(store2);
79867
+ if (store3) channels.end.publish(store3);
79234
79868
  }
79235
79869
  }
79236
79870
  module.exports = handleRequest;
@@ -83627,8 +84261,8 @@ var require_tools = __commonJS({
83627
84261
  if (asJsonChan.hasSubscribers === false) {
83628
84262
  return _asJson.call(this, obj, msg, num3, time3);
83629
84263
  }
83630
- const store2 = { instance: this, arguments };
83631
- return asJsonChan.traceSync(_asJson, store2, this, obj, msg, num3, time3);
84264
+ const store3 = { instance: this, arguments };
84265
+ return asJsonChan.traceSync(_asJson, store3, this, obj, msg, num3, time3);
83632
84266
  }
83633
84267
  function _asJson(obj, msg, num3, time3) {
83634
84268
  const stringify2 = this[stringifySym];
@@ -85896,8 +86530,8 @@ var require_error_handler = __commonJS({
85896
86530
  const result = func(error51, reply.request, reply);
85897
86531
  if (result !== void 0) {
85898
86532
  if (result !== null && typeof result.then === "function") {
85899
- const store2 = reply[kDiagnosticsStore] || null;
85900
- wrapThenable(result, reply, store2);
86533
+ const store3 = reply[kDiagnosticsStore] || null;
86534
+ wrapThenable(result, reply, store3);
85901
86535
  } else {
85902
86536
  reply.send(result);
85903
86537
  }
@@ -88216,11 +88850,11 @@ var require_request3 = __commonJS({
88216
88850
  port: {
88217
88851
  get() {
88218
88852
  const portReg = /(?<port>:\d+)$/;
88219
- const matches = portReg.exec(this.host);
88220
- if (matches === null || matches[1] === void 0) {
88853
+ const matches2 = portReg.exec(this.host);
88854
+ if (matches2 === null || matches2[1] === void 0) {
88221
88855
  return null;
88222
88856
  }
88223
- return parseInt(matches.groups.port.slice(1), 10);
88857
+ return parseInt(matches2.groups.port.slice(1), 10);
88224
88858
  }
88225
88859
  },
88226
88860
  protocol: {
@@ -91627,11 +92261,11 @@ var require_validate = __commonJS({
91627
92261
  jsonPointer = $data;
91628
92262
  data = names_1.default.rootData;
91629
92263
  } else {
91630
- const matches = RELATIVE_JSON_POINTER.exec($data);
91631
- if (!matches)
92264
+ const matches2 = RELATIVE_JSON_POINTER.exec($data);
92265
+ if (!matches2)
91632
92266
  throw new Error(`Invalid JSON-pointer: ${$data}`);
91633
- const up = +matches[1];
91634
- jsonPointer = matches[2];
92267
+ const up = +matches2[1];
92268
+ jsonPointer = matches2[2];
91635
92269
  if (jsonPointer === "#") {
91636
92270
  if (up >= dataLevel)
91637
92271
  throw new Error(errorMsg("property/index", up));
@@ -92328,11 +92962,11 @@ var require_schemes = __commonJS({
92328
92962
  urnComponent.error = "URN can not be parsed";
92329
92963
  return urnComponent;
92330
92964
  }
92331
- const matches = urnComponent.path.match(URN_REG);
92332
- if (matches) {
92965
+ const matches2 = urnComponent.path.match(URN_REG);
92966
+ if (matches2) {
92333
92967
  const scheme = options.scheme || urnComponent.scheme || "urn";
92334
- urnComponent.nid = matches[1].toLowerCase();
92335
- urnComponent.nss = matches[2];
92968
+ urnComponent.nid = matches2[1].toLowerCase();
92969
+ urnComponent.nss = matches2[2];
92336
92970
  const urnScheme = `${scheme}:${options.nid || urnComponent.nid}`;
92337
92971
  const schemeHandler = getSchemeHandler(urnScheme);
92338
92972
  urnComponent.path = void 0;
@@ -92609,8 +93243,8 @@ var require_fast_uri = __commonJS({
92609
93243
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
92610
93244
  var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
92611
93245
  var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
92612
- function getParseError(parsed, matches) {
92613
- if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
93246
+ function getParseError(parsed, matches2) {
93247
+ if (matches2[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
92614
93248
  return 'URI path must start with "/" when authority is present.';
92615
93249
  }
92616
93250
  if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
@@ -92657,19 +93291,19 @@ var require_fast_uri = __commonJS({
92657
93291
  }
92658
93292
  }
92659
93293
  }
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];
93294
+ const matches2 = uri.match(URI_PARSE);
93295
+ if (matches2) {
93296
+ parsed.scheme = matches2[1];
93297
+ parsed.userinfo = matches2[3];
93298
+ parsed.host = matches2[4];
93299
+ parsed.port = parseInt(matches2[5], 10);
93300
+ parsed.path = matches2[6] || "";
93301
+ parsed.query = matches2[7];
93302
+ parsed.fragment = matches2[8];
92669
93303
  if (isNaN(parsed.port)) {
92670
- parsed.port = matches[5];
93304
+ parsed.port = matches2[5];
92671
93305
  }
92672
- const parseError = getParseError(parsed, matches);
93306
+ const parseError = getParseError(parsed, matches2);
92673
93307
  if (parseError !== void 0) {
92674
93308
  parsed.error = parsed.error || parseError;
92675
93309
  malformedAuthorityOrPort = true;
@@ -95547,24 +96181,24 @@ var require_timestamp2 = __commonJS({
95547
96181
  }
95548
96182
  exports.default = validTimestamp;
95549
96183
  function validDate(str2) {
95550
- const matches = DATE.exec(str2);
95551
- if (!matches)
96184
+ const matches2 = DATE.exec(str2);
96185
+ if (!matches2)
95552
96186
  return false;
95553
- const y = +matches[1];
95554
- const m = +matches[2];
95555
- const d = +matches[3];
96187
+ const y = +matches2[1];
96188
+ const m = +matches2[2];
96189
+ const d = +matches2[3];
95556
96190
  return m >= 1 && m <= 12 && d >= 1 && (d <= DAYS[m] || // leap year: https://tools.ietf.org/html/rfc3339#appendix-C
95557
96191
  m === 2 && d === 29 && (y % 100 === 0 ? y % 400 === 0 : y % 4 === 0));
95558
96192
  }
95559
96193
  function validTime(str2) {
95560
- const matches = TIME.exec(str2);
95561
- if (!matches)
96194
+ const matches2 = TIME.exec(str2);
96195
+ if (!matches2)
95562
96196
  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);
96197
+ const hr = +matches2[1];
96198
+ const min = +matches2[2];
96199
+ const sec = +matches2[3];
96200
+ const tzH = +(matches2[4] || 0);
96201
+ const tzM = +(matches2[5] || 0);
95568
96202
  return hr <= 23 && min <= 59 && sec <= 59 || // leap second
95569
96203
  hr - tzH === 23 && min - tzM === 59 && sec === 60;
95570
96204
  }
@@ -96518,19 +97152,19 @@ var require_parseJson = __commonJS({
96518
97152
  function parseJson(s, pos) {
96519
97153
  let endPos;
96520
97154
  parseJson.message = void 0;
96521
- let matches;
97155
+ let matches2;
96522
97156
  if (pos)
96523
97157
  s = s.slice(pos);
96524
97158
  try {
96525
97159
  parseJson.position = pos + s.length;
96526
97160
  return JSON.parse(s);
96527
97161
  } catch (e) {
96528
- matches = rxParseJson.exec(e.message);
96529
- if (!matches) {
97162
+ matches2 = rxParseJson.exec(e.message);
97163
+ if (!matches2) {
96530
97164
  parseJson.message = "unexpected end";
96531
97165
  return void 0;
96532
97166
  }
96533
- endPos = +matches[1];
97167
+ endPos = +matches2[1];
96534
97168
  const c = s[endPos];
96535
97169
  s = s.slice(0, endPos);
96536
97170
  parseJson.position = pos + endPos;
@@ -97612,11 +98246,11 @@ var require_schemes2 = __commonJS({
97612
98246
  urnComponent.error = "URN can not be parsed";
97613
98247
  return urnComponent;
97614
98248
  }
97615
- const matches = urnComponent.path.match(URN_REG);
97616
- if (matches) {
98249
+ const matches2 = urnComponent.path.match(URN_REG);
98250
+ if (matches2) {
97617
98251
  const scheme = options.scheme || urnComponent.scheme || "urn";
97618
- urnComponent.nid = matches[1].toLowerCase();
97619
- urnComponent.nss = matches[2];
98252
+ urnComponent.nid = matches2[1].toLowerCase();
98253
+ urnComponent.nss = matches2[2];
97620
98254
  const urnScheme = `${scheme}:${options.nid || urnComponent.nid}`;
97621
98255
  const schemeHandler = getSchemeHandler(urnScheme);
97622
98256
  urnComponent.path = void 0;
@@ -97893,8 +98527,8 @@ var require_fast_uri2 = __commonJS({
97893
98527
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
97894
98528
  var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
97895
98529
  var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
97896
- function getParseError(parsed, matches) {
97897
- if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
98530
+ function getParseError(parsed, matches2) {
98531
+ if (matches2[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
97898
98532
  return 'URI path must start with "/" when authority is present.';
97899
98533
  }
97900
98534
  if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
@@ -97941,19 +98575,19 @@ var require_fast_uri2 = __commonJS({
97941
98575
  }
97942
98576
  }
97943
98577
  }
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];
98578
+ const matches2 = uri.match(URI_PARSE);
98579
+ if (matches2) {
98580
+ parsed.scheme = matches2[1] === void 0 ? void 0 : matches2[1].toLowerCase();
98581
+ parsed.userinfo = matches2[3];
98582
+ parsed.host = matches2[4];
98583
+ parsed.port = parseInt(matches2[5], 10);
98584
+ parsed.path = matches2[6] || "";
98585
+ parsed.query = matches2[7];
98586
+ parsed.fragment = matches2[8];
97953
98587
  if (isNaN(parsed.port)) {
97954
- parsed.port = matches[5];
98588
+ parsed.port = matches2[5];
97955
98589
  }
97956
- const parseError = getParseError(parsed, matches);
98590
+ const parseError = getParseError(parsed, matches2);
97957
98591
  if (parseError !== void 0) {
97958
98592
  parsed.error = parsed.error || parseError;
97959
98593
  malformedAuthorityOrPort = true;
@@ -98150,12 +98784,12 @@ var require_formats = __commonJS({
98150
98784
  var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
98151
98785
  var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
98152
98786
  function date5(str2) {
98153
- const matches = DATE.exec(str2);
98154
- if (!matches)
98787
+ const matches2 = DATE.exec(str2);
98788
+ if (!matches2)
98155
98789
  return false;
98156
- const year = +matches[1];
98157
- const month = +matches[2];
98158
- const day = +matches[3];
98790
+ const year = +matches2[1];
98791
+ const month = +matches2[2];
98792
+ const day = +matches2[3];
98159
98793
  return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]);
98160
98794
  }
98161
98795
  function compareDate(d1, d2) {
@@ -98170,16 +98804,16 @@ var require_formats = __commonJS({
98170
98804
  var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
98171
98805
  function getTime(strictTimeZone) {
98172
98806
  return function time3(str2) {
98173
- const matches = TIME.exec(str2);
98174
- if (!matches)
98807
+ const matches2 = TIME.exec(str2);
98808
+ if (!matches2)
98175
98809
  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);
98810
+ const hr = +matches2[1];
98811
+ const min = +matches2[2];
98812
+ const sec = +matches2[3];
98813
+ const tz = matches2[4];
98814
+ const tzSign = matches2[5] === "-" ? -1 : 1;
98815
+ const tzH = +(matches2[6] || 0);
98816
+ const tzM = +(matches2[7] || 0);
98183
98817
  if (tzH > 23 || tzM > 59 || strictTimeZone && !tz)
98184
98818
  return false;
98185
98819
  if (hr <= 23 && min <= 59 && sec < 60)
@@ -105663,8 +106297,8 @@ var require_http_method = __commonJS({
105663
106297
  get: (type) => {
105664
106298
  return handlers.get(type) || null;
105665
106299
  },
105666
- set: (type, store2) => {
105667
- handlers.set(type, store2);
106300
+ set: (type, store3) => {
106301
+ handlers.set(type, store3);
105668
106302
  }
105669
106303
  };
105670
106304
  },
@@ -105903,14 +106537,14 @@ var require_handler_storage = __commonJS({
105903
106537
  // Builds a store object that maps from constraint values to a bitmap of handler indexes which pass the constraint for a value
105904
106538
  // 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
106539
  // The store's implementation comes from the strategies provided to the Router.
105906
- _buildConstraintStore(store2, constraint) {
106540
+ _buildConstraintStore(store3, constraint) {
105907
106541
  for (let i = 0; i < this.handlers.length; i++) {
105908
106542
  const handler = this.handlers[i];
105909
106543
  const constraintValue = handler.constraints[constraint];
105910
106544
  if (constraintValue !== void 0) {
105911
- let indexes = store2.get(constraintValue) || 0;
106545
+ let indexes = store3.get(constraintValue) || 0;
105912
106546
  indexes |= 1 << i;
105913
- store2.set(constraintValue, indexes);
106547
+ store3.set(constraintValue, indexes);
105914
106548
  }
105915
106549
  }
105916
106550
  }
@@ -105935,9 +106569,9 @@ var require_handler_storage = __commonJS({
105935
106569
  _compileGetHandlerMatchingConstraints(constrainer) {
105936
106570
  this.constrainedHandlerStores = {};
105937
106571
  for (const constraint of this.constraints) {
105938
- const store2 = constrainer.newStoreForConstraint(constraint);
105939
- this.constrainedHandlerStores[constraint] = store2;
105940
- this._buildConstraintStore(store2, constraint);
106572
+ const store3 = constrainer.newStoreForConstraint(constraint);
106573
+ this.constrainedHandlerStores[constraint] = store3;
106574
+ this._buildConstraintStore(store3, constraint);
105941
106575
  }
105942
106576
  const lines = [];
105943
106577
  lines.push(`
@@ -106200,7 +106834,7 @@ var require_accept_version = __commonJS({
106200
106834
  this.maxMinors = {};
106201
106835
  this.maxPatches = {};
106202
106836
  }
106203
- SemVerStore.prototype.set = function(version2, store2) {
106837
+ SemVerStore.prototype.set = function(version2, store3) {
106204
106838
  if (typeof version2 !== "string") {
106205
106839
  throw new TypeError("Version should be a string");
106206
106840
  }
@@ -106213,21 +106847,21 @@ var require_accept_version = __commonJS({
106213
106847
  patch = Number(patch) || 0;
106214
106848
  if (major >= this.maxMajor) {
106215
106849
  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);
106850
+ this.store.set("x", store3);
106851
+ this.store.set("*", store3);
106852
+ this.store.set("x.x", store3);
106853
+ this.store.set("x.x.x", store3);
106220
106854
  }
106221
106855
  if (minor >= (this.maxMinors[major] || 0)) {
106222
106856
  this.maxMinors[major] = minor;
106223
- this.store.set(`${major}.x`, store2);
106224
- this.store.set(`${major}.x.x`, store2);
106857
+ this.store.set(`${major}.x`, store3);
106858
+ this.store.set(`${major}.x.x`, store3);
106225
106859
  }
106226
106860
  if (patch >= (this.maxPatches[`${major}.${minor}`] || 0)) {
106227
106861
  this.maxPatches[`${major}.${minor}`] = patch;
106228
- this.store.set(`${major}.${minor}.x`, store2);
106862
+ this.store.set(`${major}.${minor}.x`, store3);
106229
106863
  }
106230
- this.store.set(`${major}.${minor}.${patch}`, store2);
106864
+ this.store.set(`${major}.${minor}.${patch}`, store3);
106231
106865
  return this;
106232
106866
  };
106233
106867
  SemVerStore.prototype.get = function(version2) {
@@ -106695,10 +107329,10 @@ var require_find_my_way = __commonJS({
106695
107329
  this.trees = /* @__PURE__ */ Object.create(null);
106696
107330
  this._treeGET = null;
106697
107331
  }
106698
- Router.prototype.on = function on(method, path, opts, handler, store2) {
107332
+ Router.prototype.on = function on(method, path, opts, handler, store3) {
106699
107333
  if (typeof opts === "function") {
106700
107334
  if (handler !== void 0) {
106701
- store2 = handler;
107335
+ store3 = handler;
106702
107336
  }
106703
107337
  handler = opts;
106704
107338
  opts = {};
@@ -106712,8 +107346,8 @@ var require_find_my_way = __commonJS({
106712
107346
  assert2(path.length === optionalParamMatch.index + optionalParamMatch[0].length, "Optional Parameter needs to be the last parameter of the path");
106713
107347
  const pathFull = path.replace(OPTIONAL_PARAM_REGEXP, "$1$2");
106714
107348
  const pathOptional = path.replace(OPTIONAL_PARAM_REGEXP, "$2") || "/";
106715
- this.on(method, pathFull, opts, handler, store2);
106716
- this.on(method, pathOptional, opts, handler, store2);
107349
+ this.on(method, pathFull, opts, handler, store3);
107350
+ this.on(method, pathOptional, opts, handler, store3);
106717
107351
  return;
106718
107352
  }
106719
107353
  const route = path;
@@ -106727,10 +107361,10 @@ var require_find_my_way = __commonJS({
106727
107361
  for (const method2 of methods) {
106728
107362
  assert2(typeof method2 === "string", "Method should be a string");
106729
107363
  assert2(httpMethods.includes(method2), `Method '${method2}' is not an http method.`);
106730
- this._on(method2, path, opts, handler, store2, route);
107364
+ this._on(method2, path, opts, handler, store3, route);
106731
107365
  }
106732
107366
  };
106733
- Router.prototype._on = function _on(method, path, opts, handler, store2) {
107367
+ Router.prototype._on = function _on(method, path, opts, handler, store3) {
106734
107368
  let constraints = {};
106735
107369
  if (opts.constraints !== void 0) {
106736
107370
  assert2(typeof opts.constraints === "object" && opts.constraints !== null, "Constraints should be an object");
@@ -106851,7 +107485,7 @@ var require_find_my_way = __commonJS({
106851
107485
  throw new Error(`Method '${method}' already declared for route '${pattern}' with constraints '${JSON.stringify(constraints)}'`);
106852
107486
  }
106853
107487
  }
106854
- const route = { method, path, pattern, params, opts, handler, store: store2 };
107488
+ const route = { method, path, pattern, params, opts, handler, store: store3 };
106855
107489
  this.routes.push(route);
106856
107490
  currentNode.addRoute(route, this.constrainer);
106857
107491
  };
@@ -107199,8 +107833,8 @@ var require_find_my_way = __commonJS({
107199
107833
  Router.prototype._rebuild = function(routes2) {
107200
107834
  this.reset();
107201
107835
  for (const route of routes2) {
107202
- const { method, path, opts, handler, store: store2 } = route;
107203
- this._on(method, path, opts, handler, store2);
107836
+ const { method, path, opts, handler, store: store3 } = route;
107837
+ this._on(method, path, opts, handler, store3);
107204
107838
  }
107205
107839
  };
107206
107840
  Router.prototype._defaultRoute = function(req, res, ctx) {
@@ -107260,12 +107894,12 @@ var require_find_my_way = __commonJS({
107260
107894
  if (!httpMethods.hasOwnProperty(i)) continue;
107261
107895
  const m = httpMethods[i];
107262
107896
  const methodName = m.toLowerCase();
107263
- Router.prototype[methodName] = function(path, handler, store2) {
107264
- return this.on(m, path, handler, store2);
107897
+ Router.prototype[methodName] = function(path, handler, store3) {
107898
+ return this.on(m, path, handler, store3);
107265
107899
  };
107266
107900
  }
107267
- Router.prototype.all = function(path, handler, store2) {
107268
- this.on(httpMethods, path, handler, store2);
107901
+ Router.prototype.all = function(path, handler, store3) {
107902
+ this.on(httpMethods, path, handler, store3);
107269
107903
  };
107270
107904
  Router.sanitizeUrlPath = function sanitizeUrlPath(url2, useSemicolonDelimiter) {
107271
107905
  const decoded = safeDecodeURI(url2, useSemicolonDelimiter);
@@ -108145,7 +108779,7 @@ var require_dist5 = __commonJS({
108145
108779
  exports.stringifyCookie = stringifyCookie;
108146
108780
  exports.stringifySetCookie = stringifySetCookie;
108147
108781
  exports.serialize = stringifySetCookie;
108148
- exports.parseSetCookie = parseSetCookie;
108782
+ exports.parseSetCookie = parseSetCookie2;
108149
108783
  exports.stringifySetCookie = stringifySetCookie;
108150
108784
  exports.serialize = stringifySetCookie;
108151
108785
  var cookieNameRegExp = /^[\u0021-\u003A\u003C\u003E-\u007E]+$/;
@@ -108282,7 +108916,7 @@ var require_dist5 = __commonJS({
108282
108916
  }
108283
108917
  return str2;
108284
108918
  }
108285
- function parseSetCookie(str2, options) {
108919
+ function parseSetCookie2(str2, options) {
108286
108920
  const dec = options?.decode || decode4;
108287
108921
  const len = str2.length;
108288
108922
  const endIdx = endIndex(str2, 0, len);
@@ -110973,43 +111607,43 @@ var init_serve_ui = __esm({
110973
111607
  });
110974
111608
 
110975
111609
  // ../../apps/engine/src/history-routes.ts
110976
- function registerHistoryRoutes(app2, store2) {
110977
- app2.get("/history", async () => store2.index());
111610
+ function registerHistoryRoutes(app2, store3) {
111611
+ app2.get("/history", async () => store3.index());
110978
111612
  app2.get("/history/runs/:id", async (request3, reply) => {
110979
- const doc = await store2.readRun(request3.params.id);
111613
+ const doc = await store3.readRun(request3.params.id);
110980
111614
  return doc ?? reply.code(404).send({ error: "run not found in history" });
110981
111615
  });
110982
111616
  app2.get("/history/compares/:id", async (request3, reply) => {
110983
- const doc = await store2.readCompare(request3.params.id);
111617
+ const doc = await store3.readCompare(request3.params.id);
110984
111618
  return doc ?? reply.code(404).send({ error: "compare not found in history" });
110985
111619
  });
110986
111620
  app2.patch("/history/runs/:id", async (request3, reply) => {
110987
111621
  const parsed = runPatch.safeParse(request3.body);
110988
111622
  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);
111623
+ const summary = await store3.updateRun(request3.params.id, parsed.data);
110990
111624
  return summary ?? reply.code(404).send({ error: "run not found in history" });
110991
111625
  });
110992
111626
  app2.patch("/history/compares/:id", async (request3, reply) => {
110993
111627
  const parsed = comparePatch.safeParse(request3.body);
110994
111628
  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);
111629
+ const summary = await store3.updateCompare(request3.params.id, parsed.data);
110996
111630
  return summary ?? reply.code(404).send({ error: "compare not found in history" });
110997
111631
  });
110998
111632
  app2.post("/history/delete", async (request3, reply) => {
110999
111633
  const parsed = removeBody.safeParse(request3.body);
111000
111634
  if (!parsed.success) return reply.code(400).send({ error: "invalid delete", issues: parsed.error.issues });
111001
- return { removed: await store2.remove(parsed.data) };
111635
+ return { removed: await store3.remove(parsed.data) };
111002
111636
  });
111003
111637
  app2.post("/history/import", async (request3, reply) => {
111004
111638
  try {
111005
111639
  const raw = request3.body;
111006
111640
  const parsed = parseExportFile(raw);
111007
111641
  if (parsed.kind === "report") {
111008
- return { kind: "run", summary: await store2.saveRun(parsed.report, { ...raw?.meta, source: "import" }) };
111642
+ return { kind: "run", summary: await store3.saveRun(parsed.report, { ...raw?.meta, source: "import" }) };
111009
111643
  }
111010
111644
  return {
111011
111645
  kind: "compare",
111012
- summary: await store2.saveCompare({ apis: parsed.doc.apis, mode: parsed.doc.mode, name: raw?.meta?.name, source: "import" })
111646
+ summary: await store3.saveCompare({ apis: parsed.doc.apis, mode: parsed.doc.mode, name: raw?.meta?.name, source: "import" })
111013
111647
  };
111014
111648
  } catch (err) {
111015
111649
  return reply.code(400).send({ error: err instanceof Error ? err.message : "Not an APITestCenter report" });
@@ -111020,7 +111654,7 @@ function registerHistoryRoutes(app2, store2) {
111020
111654
  if (!parsed.success) return reply.code(400).send({ error: "invalid compare", issues: parsed.error.issues });
111021
111655
  const doc = { kind: "compare", apis: parsed.data.apis };
111022
111656
  if (!isCompareExport(doc)) return reply.code(400).send({ error: "each API needs a run report" });
111023
- return store2.saveCompare({
111657
+ return store3.saveCompare({
111024
111658
  apis: parsed.data.apis,
111025
111659
  mode: parsed.data.mode,
111026
111660
  name: parsed.data.name,
@@ -111031,7 +111665,7 @@ function registerHistoryRoutes(app2, store2) {
111031
111665
  const parsed = revealBody.safeParse(request3.body ?? {});
111032
111666
  if (!parsed.success) return reply.code(400).send({ error: "invalid target" });
111033
111667
  const { folder, id } = parsed.data;
111034
- return { path: await store2.reveal(folder && id ? { folder, id } : void 0) };
111668
+ return { path: await store3.reveal(folder && id ? { folder, id } : void 0) };
111035
111669
  });
111036
111670
  }
111037
111671
  var runPatch, comparePatch, removeBody, compareBody, revealBody;
@@ -111089,53 +111723,53 @@ var init_settings_routes = __esm({
111089
111723
  });
111090
111724
 
111091
111725
  // ../../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 ?? ""));
111726
+ function registerWorkspaceRoutes(app2, store3, onFolderMoved) {
111727
+ app2.get("/workspace", async () => store3.index());
111728
+ app2.get("/workspace/folders", async (request3) => store3.getFolder(request3.query.path ?? ""));
111729
+ app2.get("/workspace/folders/chain", async (request3) => store3.folderChain(request3.query.collection ?? ""));
111096
111730
  app2.put("/workspace/folders", async (request3, reply) => {
111097
111731
  const parsed = external_exports.object({ path: external_exports.string().min(1).max(400), settings: external_exports.unknown() }).strict().safeParse(request3.body);
111098
111732
  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);
111733
+ return store3.saveFolder(parsed.data.path, parsed.data.settings);
111100
111734
  });
111101
111735
  app2.post("/workspace/folders/move", async (request3, reply) => {
111102
111736
  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
111737
  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);
111738
+ const moved = await store3.moveFolder(parsed.data.from, parsed.data.to);
111105
111739
  await onFolderMoved?.(moved.from, moved.to);
111106
111740
  return moved;
111107
111741
  });
111108
111742
  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" });
111743
+ return await store3.deleteFolder(request3.query.path ?? "") ? { removed: 1 } : reply.code(404).send({ error: "folder not found" });
111110
111744
  });
111111
111745
  app2.get("/workspace/requests/:id", async (request3, reply) => {
111112
- const saved = await store2.getRequest(request3.params.id);
111746
+ const saved = await store3.getRequest(request3.params.id);
111113
111747
  return saved ?? reply.code(404).send({ error: "saved request not found" });
111114
111748
  });
111115
111749
  app2.post("/workspace/requests", async (request3, reply) => {
111116
111750
  const parsed = requestBody.safeParse(request3.body);
111117
111751
  if (!parsed.success) return reply.code(400).send({ error: "invalid request", issues: parsed.error.issues });
111118
- return store2.saveRequest(parsed.data);
111752
+ return store3.saveRequest(parsed.data);
111119
111753
  });
111120
111754
  app2.put("/workspace/requests/:id", async (request3, reply) => {
111121
111755
  const parsed = requestBody.safeParse(request3.body);
111122
111756
  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 });
111757
+ return store3.saveRequest({ ...parsed.data, id: request3.params.id });
111124
111758
  });
111125
111759
  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" });
111760
+ return await store3.deleteRequest(request3.params.id) ? { removed: 1 } : reply.code(404).send({ error: "saved request not found" });
111127
111761
  });
111128
- app2.put("/workspace/environments", async (request3) => store2.saveEnvironments(request3.body));
111762
+ app2.put("/workspace/environments", async (request3) => store3.saveEnvironments(request3.body));
111129
111763
  app2.post("/workspace/import", async (request3, reply) => {
111130
111764
  const parsed = importBody.safeParse(request3.body);
111131
111765
  if (!parsed.success) return reply.code(400).send({ error: "invalid import", issues: parsed.error.issues });
111132
111766
  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));
111767
+ for (const f of parsed.data.folders) await store3.saveFolder(f.path, f.settings);
111768
+ for (const r of parsed.data.requests) saved.push(await store3.saveRequest(r));
111135
111769
  let added = 0;
111136
111770
  let merged = 0;
111137
111771
  if (parsed.data.environments.length) {
111138
- const envs = await store2.environments();
111772
+ const envs = await store3.environments();
111139
111773
  for (const incoming of parsed.data.environments) {
111140
111774
  const existing = envs.find((e) => e.name.toLowerCase() === incoming.name.toLowerCase());
111141
111775
  if (existing) {
@@ -111147,7 +111781,7 @@ function registerWorkspaceRoutes(app2, store2, onFolderMoved) {
111147
111781
  added++;
111148
111782
  }
111149
111783
  }
111150
- await store2.saveEnvironments(envs);
111784
+ await store3.saveEnvironments(envs);
111151
111785
  }
111152
111786
  return { requests: saved.length, firstId: saved[0]?.id ?? null, environments: { added, merged } };
111153
111787
  });
@@ -111178,7 +111812,7 @@ var init_workspace_routes = __esm({
111178
111812
  });
111179
111813
 
111180
111814
  // ../../apps/engine/src/demo-api.ts
111181
- import { randomBytes as randomBytes4 } from "node:crypto";
111815
+ import { randomBytes as randomBytes5 } from "node:crypto";
111182
111816
  function registerDemoApi(api) {
111183
111817
  const tokens = /* @__PURE__ */ new Map();
111184
111818
  const created = /* @__PURE__ */ new Map();
@@ -111204,7 +111838,7 @@ function registerDemoApi(api) {
111204
111838
  return reply.code(401).send({ error: 'Use any username with the password "demo".' });
111205
111839
  }
111206
111840
  if (tokens.size > 5e3) tokens.clear();
111207
- const token = `demo-${randomBytes4(12).toString("hex")}`;
111841
+ const token = `demo-${randomBytes5(12).toString("hex")}`;
111208
111842
  tokens.set(token, username);
111209
111843
  return { data: { token, user: username, expiresIn: 3600 } };
111210
111844
  });
@@ -111534,6 +112168,8 @@ async function routes(api) {
111534
112168
  status: "ok",
111535
112169
  service: "engine",
111536
112170
  version: appVersion,
112171
+ // "cli": started by `atc ui` — the screens say so when it stops.
112172
+ host: process.env.ATC_HOST ?? (uiDir ? "desktop" : "dev"),
111537
112173
  persistence: "files",
111538
112174
  dataDir: history.dir,
111539
112175
  k6: await detectK6(root),
@@ -111560,6 +112196,13 @@ async function routes(api) {
111560
112196
  return usage.setEnabled(request3.body.enabled);
111561
112197
  });
111562
112198
  await api.register(async (demo) => registerDemoApi(demo), { prefix: "/demo-api", logLevel: "warn" });
112199
+ api.post("/graphql/introspect", async (request3, reply) => {
112200
+ try {
112201
+ return await probeFull(request3.body);
112202
+ } catch (err) {
112203
+ return reply.code(400).send({ error: err instanceof Error ? err.message : "invalid request" });
112204
+ }
112205
+ });
111563
112206
  api.post("/probe", async (request3, reply) => {
111564
112207
  const parsed = labRequestSchema.safeParse(request3.body);
111565
112208
  if (!parsed.success) {
@@ -111598,6 +112241,8 @@ async function routes(api) {
111598
112241
  useOAuthStore(secrets2);
111599
112242
  registerOAuthRoutes(api);
111600
112243
  useCertificateStore(history.dir, secrets2);
112244
+ useCookieStore(secrets2);
112245
+ registerCookieRoutes(api);
111601
112246
  registerCertificateRoutes(api);
111602
112247
  const suitePlans = new SuitePlans(history.dir);
111603
112248
  registerWorkspaceRoutes(api, workspace, (from, to) => suitePlans.move(from, to));
@@ -111639,7 +112284,7 @@ async function routes(api) {
111639
112284
  function engineUrl() {
111640
112285
  return listeningUrl;
111641
112286
  }
111642
- var import_cors, import_dotenv, import_fastify, root, host, port, history, appVersion, usage, app, listeningUrl, uiDir;
112287
+ var import_cors, import_dotenv, import_fastify, root, tlsCheckWasOff, host, port, history, appVersion, usage, app, listeningUrl, uiDir;
111643
112288
  var init_index = __esm({
111644
112289
  async "../../apps/engine/src/index.ts"() {
111645
112290
  "use strict";
@@ -111662,9 +112307,12 @@ var init_index = __esm({
111662
112307
  init_suite_run();
111663
112308
  init_oauth();
111664
112309
  init_certificates();
112310
+ init_cookies();
111665
112311
  init_local_servers();
111666
112312
  root = resolve3(dirname2(fileURLToPath(import.meta.url)), "../../..");
111667
112313
  if (!process.env.ATC_NO_DOTENV) (0, import_dotenv.config)({ path: resolve3(root, ".env") });
112314
+ tlsCheckWasOff = process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0";
112315
+ if (tlsCheckWasOff) delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
111668
112316
  host = process.env.ENGINE_HOST ?? "127.0.0.1";
111669
112317
  port = Number(process.env.ENGINE_PORT ?? 4100);
111670
112318
  history = new HistoryStore();
@@ -111696,6 +112344,7 @@ var init_index = __esm({
111696
112344
  const url2 = `http://${host}:${typeof address === "object" && address ? address.port : port}`;
111697
112345
  listeningUrl = url2;
111698
112346
  app.log.info(`engine listening on ${url2}`);
112347
+ 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
112348
  if (!process.env.ATC_NO_READY_LINE) console.log(`ATC_READY ${url2}`);
111700
112349
  } catch (err) {
111701
112350
  app.log.error(err);
@@ -111709,7 +112358,8 @@ init_src();
111709
112358
  init_draft();
111710
112359
  init_history_store();
111711
112360
  init_k6();
111712
- import { spawn as spawn3 } from "node:child_process";
112361
+ import { spawn as spawn3, spawnSync } from "node:child_process";
112362
+ import { tmpdir as tmpdir3 } from "node:os";
111713
112363
  import { randomUUID as randomUUID7 } from "node:crypto";
111714
112364
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
111715
112365
  import { readFile as readFile10, writeFile as writeFile9 } from "node:fs/promises";
@@ -111794,14 +112444,16 @@ init_secret_store();
111794
112444
  init_suite_run();
111795
112445
  init_oauth();
111796
112446
  init_certificates();
112447
+ init_cookies();
111797
112448
  init_workspace_store();
111798
112449
  var repoRoot = resolve4(dirname3(fileURLToPath2(import.meta.url)), "../../..");
111799
112450
  var EXIT = { passed: 0, failed: 1, error: 2 };
111800
- var VERSION = "0.7.0";
112451
+ var VERSION = "0.9.0";
111801
112452
  var HELP = `APITestCenter CLI ${VERSION} \u2014 https://apitestcenter.com/docs/cli-and-ci.html
111802
112453
 
111803
112454
  Usage (apitestcenter, or the short name atc once installed)
111804
- atc ui [collection] Open the app in your browser to build and edit tests
112455
+ atc ui [collection] Open the app in a window to build and edit tests
112456
+ (--browser: a tab in your usual browser; --no-open: just print the address)
111805
112457
  atc init [collection] Start api-tests/ in this repository, with a CI pipeline
111806
112458
  atc suite <collection> [options] Send every request in a collection once and check each one
111807
112459
  atc run <request> [options] Run a load test and exit 0 (passed) / 1 (failed) / 2 (error)
@@ -111886,7 +112538,8 @@ async function main() {
111886
112538
  help: { type: "boolean", short: "h" },
111887
112539
  version: { type: "boolean", short: "v" },
111888
112540
  data: { type: "string" },
111889
- "no-open": { type: "boolean" }
112541
+ "no-open": { type: "boolean" },
112542
+ browser: { type: "boolean" }
111890
112543
  }
111891
112544
  });
111892
112545
  const [command, target] = positionals;
@@ -111904,11 +112557,12 @@ async function main() {
111904
112557
  }
111905
112558
  if (command === "init") return init(target, values.data ?? "api-tests");
111906
112559
  const dataDir = resolveDataDir();
111907
- if (command === "ui") return ui(target, dataDir, values["no-open"] === true);
112560
+ if (command === "ui") return ui(target, dataDir, values["no-open"] === true, values.browser === true);
111908
112561
  const secrets2 = new SecretStore(dataDir);
111909
112562
  const workspace = new WorkspaceStore(dataDir, secrets2);
111910
112563
  useOAuthStore(secrets2);
111911
112564
  useCertificateStore(dataDir, secrets2);
112565
+ useCookieStore(secrets2);
111912
112566
  for (const spec of values.cert ?? []) {
111913
112567
  const eq = spec.indexOf("=");
111914
112568
  const files = eq > 0 ? spec.slice(eq + 1).split(",").map((f) => f.trim()).filter(Boolean) : [];
@@ -111967,7 +112621,7 @@ Environments (${index.environments.length})`);
111967
112621
  const message = err instanceof Error ? err.message : String(err);
111968
112622
  fail(message.replace(/needs an environment.*$/, "is not set \u2014 use --env <name> or --var name=value"));
111969
112623
  }
111970
- let load2 = values.preset ? applyPreset(DEFAULT_LOAD, values.preset) : { ...DEFAULT_LOAD };
112624
+ let load3 = values.preset ? applyPreset(DEFAULT_LOAD, values.preset) : { ...DEFAULT_LOAD };
111971
112625
  const num3 = (v, label) => {
111972
112626
  if (v == null) return void 0;
111973
112627
  const n = Number(v);
@@ -111984,8 +112638,8 @@ Environments (${index.environments.length})`);
111984
112638
  };
111985
112639
  if (custom2.rps != null) custom2.mode = "rate";
111986
112640
  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);
112641
+ if (Object.keys(overrides).length) load3 = { ...load3, ...overrides, stages: load3.stages && !values.duration ? load3.stages : null, preset: "custom" };
112642
+ const problem = loadProblem(load3);
111989
112643
  if (problem) fail(problem);
111990
112644
  const pct3 = (v) => v == null ? void 0 : (num3(v, "percent") ?? 0) / 100;
111991
112645
  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 +112659,7 @@ Environments (${index.environments.length})`);
112005
112659
  if (!Number.isFinite(n) || n < 0) fail("--max-slower must be a percentage, like 10");
112006
112660
  return n / 100;
112007
112661
  })();
112008
- const payload = buildPayload(draft, toLoadConfig(load2), `${name} \xB7 CLI`, targets);
112662
+ const payload = buildPayload(draft, toLoadConfig(load3), `${name} \xB7 CLI`, targets);
112009
112663
  const log = (line) => !values.quiet && console.log(line);
112010
112664
  if (draft.before) {
112011
112665
  const before = draft.before;
@@ -112036,7 +112690,7 @@ Environments (${index.environments.length})`);
112036
112690
  }
112037
112691
  }
112038
112692
  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}`);
112693
+ log(`load ${payload.request.method} ${displayTemplate(payload.request.url)} \xB7 ${load3.mode === "rate" ? `${load3.rps} req/s` : `${load3.vus} VUs`} \xB7 ${load3.duration}`);
112040
112694
  let interrupted = false;
112041
112695
  process.on("SIGINT", () => {
112042
112696
  if (interrupted) process.exit(EXIT.error);
@@ -112087,9 +112741,9 @@ compared with baseline \u201C${baseline.testName}\u201D (${baseline.startedAt.sl
112087
112741
  if (history2) log(` saved to History in ${dataDir}`);
112088
112742
  return failed ? EXIT.failed : EXIT.passed;
112089
112743
  }
112090
- async function ui(collection, dataDir, noOpen) {
112744
+ async function ui(collection, dataDir, noOpen, inBrowserTab = false) {
112091
112745
  const here = dirname3(fileURLToPath2(import.meta.url));
112092
- const uiDir2 = [resolve4(here, "ui"), resolve4(repoRoot, "apps/web/.next-desktop")].find((d) => existsSync2(resolve4(d, "index.html")));
112746
+ const uiDir2 = [process.env.ATC_UI_DIR, resolve4(here, "ui"), resolve4(repoRoot, "apps/web/.next-desktop")].find((d) => Boolean(d) && existsSync2(resolve4(d, "index.html")));
112093
112747
  if (!uiDir2) fail("this build has no app screens. Use the npm package (npx apitestcenter ui), or run: pnpm --filter @apitestcenter/desktop build:ui");
112094
112748
  Object.assign(process.env, {
112095
112749
  ATC_UI_DIR: uiDir2,
@@ -112100,7 +112754,9 @@ async function ui(collection, dataDir, noOpen) {
112100
112754
  ATC_USAGE_URL: "",
112101
112755
  ATC_NO_DOTENV: "1",
112102
112756
  ATC_NO_READY_LINE: "1",
112103
- ATC_LOG_LEVEL: process.env.ATC_LOG_LEVEL ?? "warn"
112757
+ ATC_LOG_LEVEL: process.env.ATC_LOG_LEVEL ?? "warn",
112758
+ // The screens show "stopped" when this engine goes away (a browser tab can't be closed for them).
112759
+ ATC_HOST: "cli"
112104
112760
  });
112105
112761
  if (!(await resolveK6Bin(repoRoot)).available) {
112106
112762
  const cached3 = cachedK6();
@@ -112115,10 +112771,54 @@ async function ui(collection, dataDir, noOpen) {
112115
112771
  console.log(` tests in: ${dataDir}`);
112116
112772
  console.log(" Saved secrets last until you stop it \u2014 use {{variables}} for tests you commit.");
112117
112773
  console.log(" Press Ctrl+C to stop.");
112118
- if (!noOpen) openBrowser(page2);
112774
+ if (!noOpen) {
112775
+ const win = inBrowserTab ? null : openAppWindow(page2);
112776
+ if (!win) openBrowser(page2);
112777
+ else {
112778
+ console.log(" Closing the window stops it too.");
112779
+ const stop = (why) => {
112780
+ if (why) console.log(why);
112781
+ closeWindow(win);
112782
+ process.exit(EXIT.passed);
112783
+ };
112784
+ process.on("SIGINT", () => stop("\nStopped."));
112785
+ process.on("SIGTERM", () => stop(""));
112786
+ win.on("exit", () => {
112787
+ console.log("Window closed \u2014 stopped.");
112788
+ process.exit(EXIT.passed);
112789
+ });
112790
+ }
112791
+ }
112119
112792
  return new Promise(() => {
112120
112793
  });
112121
112794
  }
112795
+ function findAppBrowser() {
112796
+ const env = process.env;
112797
+ const candidates = process.platform === "win32" ? [env["ProgramFiles(x86)"], env.ProgramFiles, env.LOCALAPPDATA].flatMap(
112798
+ (root2) => root2 ? [`${root2}\\Microsoft\\Edge\\Application\\msedge.exe`, `${root2}\\Google\\Chrome\\Application\\chrome.exe`, `${root2}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`] : []
112799
+ ) : process.platform === "darwin" ? ["/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium", "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"] : ["/usr/bin/microsoft-edge", "/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium", "/usr/bin/chromium-browser", "/usr/bin/brave-browser", "/snap/bin/chromium"];
112800
+ return candidates.find((p) => existsSync2(p)) ?? null;
112801
+ }
112802
+ function openAppWindow(url2) {
112803
+ const bin = findAppBrowser();
112804
+ if (!bin) return null;
112805
+ const profile = resolve4(tmpdir3(), "apitestcenter-ui-window");
112806
+ try {
112807
+ const child = spawn3(bin, [`--app=${url2}`, `--user-data-dir=${profile}`, "--no-first-run", "--no-default-browser-check", "--window-size=1440,900"], { stdio: "ignore", windowsHide: false });
112808
+ child.on("error", () => openBrowser(url2));
112809
+ return child;
112810
+ } catch {
112811
+ return null;
112812
+ }
112813
+ }
112814
+ function closeWindow(win) {
112815
+ if (win.exitCode !== null || !win.pid) return;
112816
+ try {
112817
+ if (process.platform === "win32") spawnSync("taskkill", ["/pid", String(win.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
112818
+ else win.kill("SIGTERM");
112819
+ } catch {
112820
+ }
112821
+ }
112122
112822
  function openBrowser(url2) {
112123
112823
  const [cmd, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url2]] : process.platform === "darwin" ? ["open", [url2]] : ["xdg-open", [url2]];
112124
112824
  try {
@@ -112134,9 +112834,9 @@ async function init(collection, folder) {
112134
112834
  fail(`${folder} already has saved requests. Open them with: npx apitestcenter ui --data ${folder}`);
112135
112835
  }
112136
112836
  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({
112837
+ const store3 = new WorkspaceStore(dir2, new SecretStore(dir2));
112838
+ await store3.saveEnvironments([{ id: randomUUID7(), name: "Local", variables: [{ key: "baseUrl", value: "http://localhost:3000", secret: false }] }]);
112839
+ await store3.saveRequest({
112140
112840
  name: "Health check",
112141
112841
  collection: name,
112142
112842
  request: { method: "GET", url: "{{baseUrl}}/health", checks: [{ type: "status", expect: "2xx" }] }