@piyushdoorwar/yamlet 1.14.0 → 1.15.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 (2) hide show
  1. package/dist/yamlet.js +325 -89
  2. package/package.json +1 -1
package/dist/yamlet.js CHANGED
@@ -17654,8 +17654,8 @@ var require_lodash = __commonJS({
17654
17654
  }
17655
17655
  value = toNumber(value);
17656
17656
  if (value === INFINITY || value === -INFINITY) {
17657
- var sign = value < 0 ? -1 : 1;
17658
- return sign * MAX_INTEGER;
17657
+ var sign2 = value < 0 ? -1 : 1;
17658
+ return sign2 * MAX_INTEGER;
17659
17659
  }
17660
17660
  return value === value ? value : 0;
17661
17661
  }
@@ -19851,8 +19851,8 @@ var require_moment = __commonJS({
19851
19851
  return units;
19852
19852
  }
19853
19853
  function zeroFill(number, targetLength, forceSign) {
19854
- var absNumber = "" + Math.abs(number), zerosToFill = targetLength - absNumber.length, sign2 = number >= 0;
19855
- return (sign2 ? forceSign ? "+" : "" : "-") + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
19854
+ var absNumber = "" + Math.abs(number), zerosToFill = targetLength - absNumber.length, sign3 = number >= 0;
19855
+ return (sign3 ? forceSign ? "+" : "" : "-") + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
19856
19856
  }
19857
19857
  var formattingTokens = /(\[[^\[]*\])|(\\e)|(\\)?(eHHmm|[Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g, localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, formatFunctions = {}, formatTokenFunctions = {};
19858
19858
  function addFormatToken(token2, padded, ordinal3, callback) {
@@ -21955,12 +21955,12 @@ var require_moment = __commonJS({
21955
21955
  }
21956
21956
  function offset(token2, separator) {
21957
21957
  addFormatToken(token2, 0, 0, function() {
21958
- var offset2 = this.utcOffset(), sign2 = "+";
21958
+ var offset2 = this.utcOffset(), sign3 = "+";
21959
21959
  if (offset2 < 0) {
21960
21960
  offset2 = -offset2;
21961
- sign2 = "-";
21961
+ sign3 = "-";
21962
21962
  }
21963
- return sign2 + zeroFill(~~(offset2 / 60), 2) + separator + zeroFill(~~offset2 % 60, 2);
21963
+ return sign3 + zeroFill(~~(offset2 / 60), 2) + separator + zeroFill(~~offset2 % 60, 2);
21964
21964
  });
21965
21965
  }
21966
21966
  offset("Z", ":");
@@ -22120,7 +22120,7 @@ var require_moment = __commonJS({
22120
22120
  }
22121
22121
  var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/, isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
22122
22122
  function createDuration(input, key) {
22123
- var duration = input, match = null, sign2, ret, diffRes;
22123
+ var duration = input, match = null, sign3, ret, diffRes;
22124
22124
  if (isDuration(input)) {
22125
22125
  duration = {
22126
22126
  ms: input._milliseconds,
@@ -22135,26 +22135,26 @@ var require_moment = __commonJS({
22135
22135
  duration.milliseconds = +input;
22136
22136
  }
22137
22137
  } else if (match = aspNetRegex.exec(input)) {
22138
- sign2 = match[1] === "-" ? -1 : 1;
22138
+ sign3 = match[1] === "-" ? -1 : 1;
22139
22139
  duration = {
22140
22140
  y: 0,
22141
- d: toInt(match[DATE]) * sign2,
22142
- h: toInt(match[HOUR]) * sign2,
22143
- m: toInt(match[MINUTE]) * sign2,
22144
- s: toInt(match[SECOND]) * sign2,
22145
- ms: toInt(absRound(match[MILLISECOND] * 1e3)) * sign2
22141
+ d: toInt(match[DATE]) * sign3,
22142
+ h: toInt(match[HOUR]) * sign3,
22143
+ m: toInt(match[MINUTE]) * sign3,
22144
+ s: toInt(match[SECOND]) * sign3,
22145
+ ms: toInt(absRound(match[MILLISECOND] * 1e3)) * sign3
22146
22146
  // the millisecond decimal point is included in the match
22147
22147
  };
22148
22148
  } else if (match = isoRegex.exec(input)) {
22149
- sign2 = match[1] === "-" ? -1 : 1;
22149
+ sign3 = match[1] === "-" ? -1 : 1;
22150
22150
  duration = {
22151
- y: parseIso(match[2], sign2),
22152
- M: parseIso(match[3], sign2),
22153
- w: parseIso(match[4], sign2),
22154
- d: parseIso(match[5], sign2),
22155
- h: parseIso(match[6], sign2),
22156
- m: parseIso(match[7], sign2),
22157
- s: parseIso(match[8], sign2)
22151
+ y: parseIso(match[2], sign3),
22152
+ M: parseIso(match[3], sign3),
22153
+ w: parseIso(match[4], sign3),
22154
+ d: parseIso(match[5], sign3),
22155
+ h: parseIso(match[6], sign3),
22156
+ m: parseIso(match[7], sign3),
22157
+ s: parseIso(match[8], sign3)
22158
22158
  };
22159
22159
  } else if (duration == null) {
22160
22160
  duration = {};
@@ -22178,9 +22178,9 @@ var require_moment = __commonJS({
22178
22178
  }
22179
22179
  createDuration.fn = Duration.prototype;
22180
22180
  createDuration.invalid = createInvalid;
22181
- function parseIso(inp, sign2) {
22181
+ function parseIso(inp, sign3) {
22182
22182
  var res = inp && parseFloat(inp.replace(",", "."));
22183
- return (isNaN(res) ? 0 : res) * sign2;
22183
+ return (isNaN(res) ? 0 : res) * sign3;
22184
22184
  }
22185
22185
  function positiveMomentsDifference(base2, other) {
22186
22186
  var res = {};
@@ -23549,7 +23549,7 @@ var require_moment = __commonJS({
23549
23549
  return locale2.postformat(output);
23550
23550
  }
23551
23551
  var abs = Math.abs;
23552
- function sign(x) {
23552
+ function sign2(x) {
23553
23553
  return (x > 0) - (x < 0) || +x;
23554
23554
  }
23555
23555
  function toISOString() {
@@ -23568,9 +23568,9 @@ var require_moment = __commonJS({
23568
23568
  months2 %= 12;
23569
23569
  s = seconds2 ? seconds2.toFixed(3).replace(/\.?0+$/, "") : "";
23570
23570
  totalSign = total < 0 ? "-" : "";
23571
- ymSign = sign(this._months) !== sign(total) ? "-" : "";
23572
- daysSign = sign(this._days) !== sign(total) ? "-" : "";
23573
- hmsSign = sign(this._milliseconds) !== sign(total) ? "-" : "";
23571
+ ymSign = sign2(this._months) !== sign2(total) ? "-" : "";
23572
+ daysSign = sign2(this._days) !== sign2(total) ? "-" : "";
23573
+ hmsSign = sign2(this._milliseconds) !== sign2(total) ? "-" : "";
23574
23574
  return totalSign + "P" + (years2 ? ymSign + years2 + "Y" : "") + (months2 ? ymSign + months2 + "M" : "") + (days2 ? daysSign + days2 + "D" : "") + (hours2 || minutes2 || seconds2 ? "T" : "") + (hours2 ? hmsSign + hours2 + "H" : "") + (minutes2 ? hmsSign + minutes2 + "M" : "") + (seconds2 ? hmsSign + s + "S" : "");
23575
23575
  }
23576
23576
  var proto = Duration.prototype;
@@ -24166,8 +24166,9 @@ function digestAuthorization(c, opts) {
24166
24166
  const nc = (opts.nc ?? 1).toString(16).padStart(8, "0");
24167
24167
  let ha1 = H(`${opts.username}:${c.realm}:${opts.password}`);
24168
24168
  if (alg.endsWith("-SESS")) ha1 = H(`${ha1}:${c.nonce}:${cnonce}`);
24169
- const ha2 = H(`${opts.method}:${opts.uri}`);
24170
- const qop = c.qop?.split(",").map((q2) => q2.trim()).find((q2) => q2 === "auth");
24169
+ const offered = c.qop?.split(",").map((q2) => q2.trim().toLowerCase()) ?? [];
24170
+ const qop = offered.includes("auth") ? "auth" : offered.includes("auth-int") ? "auth-int" : void 0;
24171
+ const ha2 = qop === "auth-int" ? H(`${opts.method}:${opts.uri}:${createHash(hashName).update(opts.body ?? "").digest("hex")}`) : H(`${opts.method}:${opts.uri}`);
24171
24172
  const response = qop ? H(`${ha1}:${c.nonce}:${nc}:${cnonce}:${qop}:${ha2}`) : H(`${ha1}:${c.nonce}:${ha2}`);
24172
24173
  const q = (s) => `"${s.replace(/(["\\])/g, "\\$1")}"`;
24173
24174
  const parts = [
@@ -24213,18 +24214,48 @@ function signAwsV4(opts) {
24213
24214
  const canonicalHeaders = names.map((k) => `${k}:${signed.get(k)}
24214
24215
  `).join("");
24215
24216
  const signedHeaders = names.join(";");
24216
- const path5 = url2.pathname || "/";
24217
- const canonicalPath = service === "s3" ? path5 : path5.split("/").map((seg) => awsEncode(decodeSafe(seg))).map((seg) => awsEncode(seg)).join("/");
24218
- const canonicalQuery = [...url2.searchParams.entries()].map(([k, v]) => [awsEncode(k), awsEncode(v)]).sort(([a, x], [b, y]) => a < b ? -1 : a > b ? 1 : x < y ? -1 : x > y ? 1 : 0).map(([k, v]) => `${k}=${v}`).join("&");
24219
- const canonicalRequest = [opts.method.toUpperCase(), canonicalPath, canonicalQuery, canonicalHeaders, signedHeaders, payloadHash].join("\n");
24217
+ const canonicalRequest = [opts.method.toUpperCase(), canonicalPath(url2, service), canonicalQuery([...url2.searchParams.entries()]), canonicalHeaders, signedHeaders, payloadHash].join("\n");
24220
24218
  const scope = `${date}/${region}/${service}/aws4_request`;
24221
- const stringToSign = ["AWS4-HMAC-SHA256", amzDate, scope, sha256(canonicalRequest)].join("\n");
24222
- const kDate = hmac(`AWS4${opts.aws.secretKey}`, date);
24223
- const kSigning = hmac(hmac(hmac(kDate, region), service), "aws4_request");
24224
- const signature = createHmac("sha256", kSigning).update(stringToSign).digest("hex");
24219
+ const signature = sign(canonicalRequest, { amzDate, date, region, service, scope, secretKey: opts.aws.secretKey });
24225
24220
  added.push({ key: "Authorization", value: `AWS4-HMAC-SHA256 Credential=${opts.aws.accessKey}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`, enabled: true });
24226
24221
  return added;
24227
24222
  }
24223
+ function canonicalPath(url2, service) {
24224
+ const once = (url2.pathname || "/").split("/").map((seg) => awsEncode(decodeSafe(seg)));
24225
+ return (service === "s3" ? once : once.map((seg) => awsEncode(seg))).join("/");
24226
+ }
24227
+ function canonicalQuery(entries) {
24228
+ return entries.map(([k, v]) => [awsEncode(k), awsEncode(v)]).sort(([a, x], [b, y]) => a < b ? -1 : a > b ? 1 : x < y ? -1 : x > y ? 1 : 0).map(([k, v]) => `${k}=${v}`).join("&");
24229
+ }
24230
+ function sign(canonicalRequest, s) {
24231
+ const stringToSign = ["AWS4-HMAC-SHA256", s.amzDate, s.scope, sha256(canonicalRequest)].join("\n");
24232
+ const kDate = hmac(`AWS4${s.secretKey}`, s.date);
24233
+ const kSigning = hmac(hmac(hmac(kDate, s.region), s.service), "aws4_request");
24234
+ return createHmac("sha256", kSigning).update(stringToSign).digest("hex");
24235
+ }
24236
+ function presignAwsV4(opts) {
24237
+ const url2 = new URL(opts.url);
24238
+ const guessed = awsScopeFromHost(url2.hostname);
24239
+ const region = opts.aws.region.trim() || guessed.region || "us-east-1";
24240
+ const service = opts.aws.service.trim() || guessed.service || "execute-api";
24241
+ const amzDate = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:-]|\.\d{3}/g, "");
24242
+ const date = amzDate.slice(0, 8);
24243
+ const scope = `${date}/${region}/${service}/aws4_request`;
24244
+ const entries = [
24245
+ ...[...url2.searchParams.entries()].filter(([k]) => !k.toLowerCase().startsWith("x-amz-")),
24246
+ ["X-Amz-Algorithm", "AWS4-HMAC-SHA256"],
24247
+ ["X-Amz-Credential", `${opts.aws.accessKey}/${scope}`],
24248
+ ["X-Amz-Date", amzDate],
24249
+ ["X-Amz-Expires", String(opts.expiresSeconds ?? 3600)],
24250
+ ["X-Amz-SignedHeaders", "host"]
24251
+ ];
24252
+ if (opts.aws.sessionToken.trim()) entries.push(["X-Amz-Security-Token", opts.aws.sessionToken.trim()]);
24253
+ const query = canonicalQuery(entries);
24254
+ const canonicalRequest = [opts.method.toUpperCase(), canonicalPath(url2, service), query, `host:${url2.host}
24255
+ `, "host", service === "s3" ? "UNSIGNED-PAYLOAD" : sha256(opts.body ?? "")].join("\n");
24256
+ const signature = sign(canonicalRequest, { amzDate, date, region, service, scope, secretKey: opts.aws.secretKey });
24257
+ return `${url2.origin}${url2.pathname}?${query}&X-Amz-Signature=${signature}${url2.hash}`;
24258
+ }
24228
24259
  function decodeSafe(s) {
24229
24260
  try {
24230
24261
  return decodeURIComponent(s);
@@ -24329,7 +24360,10 @@ function parseTokenBody(text3, contentType) {
24329
24360
  const result = { accessToken, raw };
24330
24361
  if (typeof o.refresh_token === "string" && o.refresh_token) result.refreshToken = o.refresh_token;
24331
24362
  if (typeof o.token_type === "string" && o.token_type) result.tokenType = o.token_type;
24332
- if (Number.isFinite(expires) && expires > 0) result.expiresIn = expires;
24363
+ if (Number.isFinite(expires) && expires > 0) {
24364
+ result.expiresIn = expires;
24365
+ result.expiresAt = Date.now() + expires * 1e3;
24366
+ }
24333
24367
  return result;
24334
24368
  }
24335
24369
  async function requestToken(cfg, resolve2, form, opts = {}) {
@@ -24388,7 +24422,8 @@ async function refreshAccessToken(cfg, resolve2, opts) {
24388
24422
  const form = { grant_type: "refresh_token", refresh_token: refreshToken };
24389
24423
  const scope = resolve2(cfg.scope).trim();
24390
24424
  if (scope) form.scope = scope;
24391
- const result = await requestToken(cfg, resolve2, form, opts);
24425
+ const refreshUrl = resolve2(cfg.refreshTokenUrl ?? "").trim();
24426
+ const result = await requestToken(refreshUrl ? { ...cfg, accessTokenUrl: cfg.refreshTokenUrl } : cfg, resolve2, form, opts);
24392
24427
  if (!result.refreshToken) result.refreshToken = refreshToken;
24393
24428
  return result;
24394
24429
  }
@@ -24396,7 +24431,13 @@ var cache = /* @__PURE__ */ new Map();
24396
24431
  var EXPIRY_MARGIN_MS = 3e4;
24397
24432
  var DEFAULT_LIFETIME_S = 3600;
24398
24433
  function cacheKey(cfg, resolve2) {
24399
- return [cfg.grantType, resolve2(cfg.accessTokenUrl), resolve2(cfg.clientId), resolve2(cfg.scope), resolve2(cfg.username), cfg.clientAuthentication, resolve2(cfg.audience ?? ""), resolve2(cfg.resource ?? "")].join("|");
24434
+ return [cfg.grantType, resolve2(cfg.accessTokenUrl), resolve2(cfg.clientId), resolve2(cfg.clientSecret), resolve2(cfg.scope), resolve2(cfg.username), resolve2(cfg.password), cfg.clientAuthentication, resolve2(cfg.audience ?? ""), resolve2(cfg.resource ?? "")].join("|");
24435
+ }
24436
+ function forgetCachedToken(cfg, resolve2) {
24437
+ cache.delete(cacheKey(cfg, resolve2));
24438
+ }
24439
+ function isExpired(expiresAt) {
24440
+ return !!expiresAt && expiresAt - EXPIRY_MARGIN_MS < Date.now();
24400
24441
  }
24401
24442
  function canFetchAutomatically(cfg, resolve2) {
24402
24443
  if (cfg.grantType !== "client_credentials" && cfg.grantType !== "password") return false;
@@ -29433,24 +29474,24 @@ function parse(formula) {
29433
29474
  }
29434
29475
  let idx = 0;
29435
29476
  let a = 0;
29436
- let sign = readSign();
29477
+ let sign2 = readSign();
29437
29478
  let number = readNumber();
29438
29479
  if (idx < formula.length && formula.charAt(idx) === "n") {
29439
29480
  idx++;
29440
- a = sign * (number !== null && number !== void 0 ? number : 1);
29481
+ a = sign2 * (number !== null && number !== void 0 ? number : 1);
29441
29482
  skipWhitespace();
29442
29483
  if (idx < formula.length) {
29443
- sign = readSign();
29484
+ sign2 = readSign();
29444
29485
  skipWhitespace();
29445
29486
  number = readNumber();
29446
29487
  } else {
29447
- sign = number = 0;
29488
+ sign2 = number = 0;
29448
29489
  }
29449
29490
  }
29450
29491
  if (number === null || idx < formula.length) {
29451
29492
  throw new Error(`n-th rule couldn't be parsed ('${formula}')`);
29452
29493
  }
29453
- return [a, sign * number];
29494
+ return [a, sign2 * number];
29454
29495
  function readSign() {
29455
29496
  if (formula.charAt(idx) === "-") {
29456
29497
  idx++;
@@ -32458,8 +32499,9 @@ async function exchange(opts) {
32458
32499
  await res.body.dump();
32459
32500
  redirects.push({ statusCode: res.statusCode, url: url2 });
32460
32501
  const next2 = new URL(location, url2);
32461
- if (next2.host !== new URL(url2).host && !opts.keepAuth) {
32462
- headers = headers.filter((h) => !["authorization", "cookie", "x-amz-security-token", "x-amz-date", "x-amz-content-sha256"].includes(h.key.toLowerCase()));
32502
+ if (next2.origin !== new URL(url2).origin && !opts.keepAuth) {
32503
+ const drop = /* @__PURE__ */ new Set(["authorization", "proxy-authorization", "cookie", "x-amz-security-token", "x-amz-date", "x-amz-content-sha256", ...(opts.credentialHeaders ?? []).map((h) => h.toLowerCase())]);
32504
+ headers = headers.filter((h) => !drop.has(h.key.toLowerCase()));
32463
32505
  explicitCookie = void 0;
32464
32506
  }
32465
32507
  if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && method === "POST" && !opts.keepMethod) {
@@ -32678,7 +32720,7 @@ async function execute(input) {
32678
32720
  if (req.preRequestScript?.trim()) {
32679
32721
  const before2 = tests.length;
32680
32722
  try {
32681
- await runScript({ ...scriptBase, script: req.preRequestScript, phase: "pre", throwOnFailure: input.throwOnTestFailure });
32723
+ await runScript({ ...scriptBase, script: req.preRequestScript, phase: "pre" });
32682
32724
  requestTests += tests.length - before2;
32683
32725
  } catch (e) {
32684
32726
  return finish(errorResponse(emptyResponse(req.method, req.url), `Pre-request script error: ${errMessage2(e)}`));
@@ -32691,14 +32733,35 @@ async function execute(input) {
32691
32733
  const resolve2 = (s) => resolveVariables(s ?? "", ctx);
32692
32734
  const auth = effectiveAuth(req, collection, folders);
32693
32735
  let oauthToken;
32694
- if (auth.type === "oauth2" && canFetchAutomatically(auth.oauth2, resolve2)) {
32695
- try {
32696
- oauthToken = (await getCachedToken(auth.oauth2, resolve2, { dispatcher, signal: input.signal })).accessToken;
32697
- } catch (e) {
32698
- return finish(errorResponse(emptyResponse(req.method, resolve2(req.url)), `OAuth 2.0 token request failed: ${errMessage2(e)}`));
32736
+ const remember = (t) => {
32737
+ const source = req.auth.type === "inherit" ? folders.some((f) => f.auth === auth) ? "folder" : "collection" : "request";
32738
+ oauthRefreshed = { accessToken: t.accessToken, refreshToken: t.refreshToken, expiresAt: t.expiresAt, source };
32739
+ if (source === "folder") oauthRefreshed.folderId = [...folders].reverse().find((f) => f.auth === auth)?.id;
32740
+ };
32741
+ if (auth.type === "oauth2") {
32742
+ const cfg = auth.oauth2;
32743
+ const known = refreshedTokenFor(cfg, resolve2);
32744
+ if (known) oauthToken = known.accessToken;
32745
+ const current = known?.accessToken ?? resolve2(cfg.accessToken).trim();
32746
+ let usable = !!current && !isExpired(known ? known.expiresAt : cfg.expiresAt);
32747
+ if (!usable && current && resolve2(cfg.refreshToken).trim()) {
32748
+ try {
32749
+ const t = await refreshAndRemember(cfg, resolve2, { dispatcher, signal: input.signal });
32750
+ oauthToken = t.accessToken;
32751
+ remember(t);
32752
+ usable = true;
32753
+ logs.push("OAuth 2.0: the access token had expired; got a new one with the refresh token.");
32754
+ } catch (e) {
32755
+ logs.push(`[warn] OAuth 2.0: refreshing the expired token failed: ${errMessage2(e)}`);
32756
+ }
32757
+ }
32758
+ if (!usable && canFetchAutomatically(cfg, resolve2)) {
32759
+ try {
32760
+ oauthToken = (await getCachedToken(cfg, resolve2, { dispatcher, signal: input.signal })).accessToken;
32761
+ } catch (e) {
32762
+ return finish(errorResponse(emptyResponse(req.method, resolve2(req.url)), `OAuth 2.0 token request failed: ${errMessage2(e)}`));
32763
+ }
32699
32764
  }
32700
- } else if (auth.type === "oauth2") {
32701
- oauthToken = refreshedTokenFor(auth.oauth2, resolve2)?.accessToken;
32702
32765
  }
32703
32766
  let built;
32704
32767
  try {
@@ -32720,7 +32783,12 @@ async function execute(input) {
32720
32783
  let incomplete;
32721
32784
  const withBodyType = (list) => prepared.contentType ? [...list.filter((h) => h.key.toLowerCase() !== "content-type"), { key: "Content-Type", value: prepared.contentType, enabled: true }] : list;
32722
32785
  let headers = withBodyType(built.headers);
32723
- if (auth.type === "awsv4") {
32786
+ if (auth.type === "awsv4" && auth.aws.addToQuery) {
32787
+ const a = auth.aws;
32788
+ const aws = { accessKey: resolve2(a.accessKey), secretKey: resolve2(a.secretKey), sessionToken: resolve2(a.sessionToken), region: resolve2(a.region), service: resolve2(a.service) };
32789
+ built = { ...built, url: presignAwsV4({ method: built.method, url: built.url, aws, body: prepared.body }) };
32790
+ headers = headers.filter((h) => h.key.toLowerCase() !== "authorization");
32791
+ } else if (auth.type === "awsv4") {
32724
32792
  const a = auth.aws;
32725
32793
  headers = [
32726
32794
  ...headers.filter((h) => h.key.toLowerCase() !== "authorization"),
@@ -32736,7 +32804,8 @@ async function execute(input) {
32736
32804
  const send = (extra = [], live = true) => exchange({
32737
32805
  url: built.url,
32738
32806
  method: built.method,
32739
- headers: [...headers, ...extra],
32807
+ // Headers computed at send time (Digest) replace ones of the same name.
32808
+ headers: [...headers.filter((h) => !extra.some((x) => x.key.toLowerCase() === h.key.toLowerCase())), ...extra],
32740
32809
  body: prepared.body,
32741
32810
  dispatcher,
32742
32811
  signal,
@@ -32744,6 +32813,7 @@ async function execute(input) {
32744
32813
  maxRedirects: settings.maxRedirects,
32745
32814
  keepMethod: settings.followOriginalMethod,
32746
32815
  keepAuth: settings.followAuthorizationHeader,
32816
+ credentialHeaders: auth.type === "apikey" && auth.apiKeyIn !== "query" && resolve2(auth.apiKeyName).trim() ? [resolve2(auth.apiKeyName).trim()] : void 0,
32747
32817
  jar: settings.disableCookieJar ? void 0 : input.cookieJar,
32748
32818
  onHead: input.onHead && live ? (status, headers2, firstByteMs) => input.onHead({ statusCode: status, reasonPhrase: STATUS_CODES[status] ?? "", headers: toHeaderList(headers2), contentType: headerValue(headers2, "content-type") ?? "", firstByteMs: Math.round(firstByteMs) }) : void 0,
32749
32819
  onChunk: live ? input.onChunk : void 0,
@@ -32755,21 +32825,30 @@ async function execute(input) {
32755
32825
  const challenge = probe.status === 401 ? parseDigestChallenge(headerValue(probe.headers, "www-authenticate")) : void 0;
32756
32826
  if (challenge) {
32757
32827
  const u = new URL(built.url);
32758
- const authorization = digestAuthorization(challenge, { username: resolve2(auth.username), password: resolve2(auth.password), method: built.method, uri: u.pathname + u.search });
32828
+ const authorization = digestAuthorization(challenge, { username: resolve2(auth.username), password: resolve2(auth.password), method: built.method, uri: u.pathname + u.search, body: prepared.body });
32759
32829
  ex = await send([{ key: "Authorization", value: authorization, enabled: true }]);
32760
32830
  } else ex = probe;
32761
32831
  } else ex = await send();
32762
- if (ex.status === 401 && auth.type === "oauth2" && !canFetchAutomatically(auth.oauth2, resolve2) && resolve2(auth.oauth2.refreshToken).trim()) {
32832
+ if (ex.status === 401 && auth.type === "oauth2") {
32833
+ const cfg = auth.oauth2;
32834
+ let token;
32763
32835
  try {
32764
- const token = await refreshAndRemember(auth.oauth2, resolve2, { dispatcher, signal });
32836
+ if (!oauthRefreshed && resolve2(cfg.refreshToken).trim()) {
32837
+ token = await refreshAndRemember(cfg, resolve2, { dispatcher, signal });
32838
+ logs.push("OAuth 2.0: the access token was rejected; got a new one with the refresh token and sent again.");
32839
+ } else if (canFetchAutomatically(cfg, resolve2)) {
32840
+ forgetCachedToken(cfg, resolve2);
32841
+ token = await getCachedToken(cfg, resolve2, { dispatcher, signal });
32842
+ logs.push("OAuth 2.0: the access token was rejected; fetched a new one and sent again.");
32843
+ }
32844
+ } catch (e) {
32845
+ logs.push(`[warn] OAuth 2.0: getting a new token failed: ${errMessage2(e)}`);
32846
+ }
32847
+ if (token) {
32765
32848
  built = buildRequest(req, { ctx, collection, folders, oauthToken: token.accessToken });
32766
32849
  headers = withBodyType(built.headers);
32767
- oauthRefreshed = { accessToken: token.accessToken, refreshToken: token.refreshToken, source: req.auth.type === "inherit" ? folders.some((f) => f.auth === auth) ? "folder" : "collection" : "request" };
32768
- if (oauthRefreshed.source === "folder") oauthRefreshed.folderId = [...folders].reverse().find((f) => f.auth === auth)?.id;
32769
- logs.push("OAuth 2.0: the access token was rejected; got a new one with the refresh token and sent again.");
32850
+ remember(token);
32770
32851
  ex = await send();
32771
- } catch (e) {
32772
- logs.push(`[warn] OAuth 2.0: refreshing the token failed: ${errMessage2(e)}`);
32773
32852
  }
32774
32853
  }
32775
32854
  if (ex.truncated) {
@@ -32809,7 +32888,7 @@ async function execute(input) {
32809
32888
  if (req.postResponseScript?.trim()) {
32810
32889
  const before2 = tests.length;
32811
32890
  try {
32812
- await runScript({ ...scriptBase, script: req.postResponseScript, phase: "post", response, throwOnFailure: input.throwOnTestFailure });
32891
+ await runScript({ ...scriptBase, script: req.postResponseScript, phase: "post", response });
32813
32892
  } catch (e) {
32814
32893
  const message = errMessage2(e);
32815
32894
  const last2 = tests.at(-1);
@@ -33073,6 +33152,7 @@ function parseDataFile(text3, filename) {
33073
33152
  }
33074
33153
 
33075
33154
  // core/src/workspaceStore.ts
33155
+ import { createHash as createHash2 } from "node:crypto";
33076
33156
  import { existsSync as existsSync3, promises as fs2, statSync as statSync2 } from "node:fs";
33077
33157
  import path4 from "node:path";
33078
33158
 
@@ -33080,6 +33160,7 @@ import path4 from "node:path";
33080
33160
  var environmentScope = (id) => `environment:${id}`;
33081
33161
  var collectionScope = (id) => `collection:${id}`;
33082
33162
  var GLOBALS_SCOPE = "globals";
33163
+ var TOKENS_SCOPE = "oauth-tokens";
33083
33164
  var currentScope = (scope) => `current:${scope}`;
33084
33165
 
33085
33166
  // core/src/pathNaming.ts
@@ -33368,6 +33449,7 @@ function readOAuth2(o) {
33368
33449
  refreshToken: get2("refreshToken", "refresh_token") ?? "",
33369
33450
  headerPrefix: get2("headerPrefix", "tokenType")?.trim() || d.headerPrefix,
33370
33451
  accessTokenUrl: get2("accessTokenUrl", "tokenUrl", "access_token_url") ?? "",
33452
+ ...get2("refreshTokenUrl", "refresh_token_url") ? { refreshTokenUrl: get2("refreshTokenUrl", "refresh_token_url") } : {},
33371
33453
  authUrl: get2("authUrl", "authorizationUrl", "auth_url") ?? "",
33372
33454
  clientId: get2("clientId", "client_id") ?? "",
33373
33455
  clientSecret: get2("clientSecret", "client_secret") ?? "",
@@ -33399,7 +33481,8 @@ function readAuth(raw, fallback = "none") {
33399
33481
  secretKey: aws("secretKey"),
33400
33482
  sessionToken: aws("sessionToken"),
33401
33483
  region: aws("region") || a.aws.region,
33402
- service: aws("service")
33484
+ service: aws("service"),
33485
+ ...["true", "1"].includes((aws("addAuthDataToQuery") || aws("addToQuery")).toLowerCase()) ? { addToQuery: true } : {}
33403
33486
  };
33404
33487
  a.apiKeyName = credential(raw.apikey, "key") ?? (type === "apikey" ? credential(raw.credentials, "key") : void 0) ?? str(raw.key);
33405
33488
  a.apiKeyValue = credential(raw.apikey, "value") ?? (type === "apikey" ? credential(raw.credentials, "value") : void 0) ?? str(raw.value);
@@ -33417,6 +33500,7 @@ function writeOAuth2(o) {
33417
33500
  if (v) out[k] = v;
33418
33501
  };
33419
33502
  put2("accessTokenUrl", o.accessTokenUrl);
33503
+ put2("refreshTokenUrl", o.refreshTokenUrl);
33420
33504
  put2("authUrl", o.authUrl);
33421
33505
  put2("clientId", o.clientId);
33422
33506
  put2("clientSecret", o.clientSecret);
@@ -33424,8 +33508,6 @@ function writeOAuth2(o) {
33424
33508
  put2("redirect_uri", o.redirectUri);
33425
33509
  put2("username", o.username);
33426
33510
  put2("password", o.password);
33427
- put2("accessToken", o.accessToken);
33428
- put2("refreshToken", o.refreshToken);
33429
33511
  if (o.headerPrefix && o.headerPrefix !== "Bearer") out.tokenType = o.headerPrefix;
33430
33512
  out.addTokenTo = o.addTokenTo === "query" ? "queryParams" : "header";
33431
33513
  out.client_authentication = o.clientAuthentication === "body" ? "body" : "header";
@@ -33454,6 +33536,8 @@ var MODELED_CREDENTIALS = new Set(
33454
33536
  "accessTokenUrl",
33455
33537
  "tokenUrl",
33456
33538
  "access_token_url",
33539
+ "refreshTokenUrl",
33540
+ "refresh_token_url",
33457
33541
  "authUrl",
33458
33542
  "authorizationUrl",
33459
33543
  "auth_url",
@@ -33476,10 +33560,19 @@ var MODELED_CREDENTIALS = new Set(
33476
33560
  "secretKey",
33477
33561
  "sessionToken",
33478
33562
  "region",
33479
- "service"
33563
+ "service",
33564
+ "addAuthDataToQuery",
33565
+ "addToQuery"
33480
33566
  ].map((k) => k.toLowerCase())
33481
33567
  );
33482
- var normalizedAuth = (v) => writeAuth(readAuth(v, "none"));
33568
+ var TOKEN_KEYS = /* @__PURE__ */ new Set(["accesstoken", "access_token", "refreshtoken", "refresh_token"]);
33569
+ function hasTokenIn(v) {
33570
+ if (Array.isArray(v)) return v.some(hasTokenIn);
33571
+ if (!isObj(v)) return false;
33572
+ if (typeof v.key === "string" && TOKEN_KEYS.has(v.key.toLowerCase()) && v.value) return true;
33573
+ return Object.entries(v).some(([k, val2]) => TOKEN_KEYS.has(k.toLowerCase()) && !!val2 || hasTokenIn(val2));
33574
+ }
33575
+ var normalizedAuth = (v) => ({ ...writeAuth(readAuth(v, "none")), ...hasTokenIn(v) ? { token: true } : {} });
33483
33576
  function mergeAuth(ours, original) {
33484
33577
  if (!isObj(original) || str(original.type).toLowerCase() !== str(ours.type).toLowerCase() || !isObj(original.credentials)) return ours;
33485
33578
  const extra = Object.entries(original.credentials).filter(([k]) => !MODELED_CREDENTIALS.has(k.toLowerCase()));
@@ -33507,6 +33600,7 @@ function writeAuth(a) {
33507
33600
  put2("sessionToken", a.aws.sessionToken);
33508
33601
  put2("region", a.aws.region);
33509
33602
  put2("service", a.aws.service);
33603
+ if (a.aws.addToQuery) creds.addAuthDataToQuery = true;
33510
33604
  break;
33511
33605
  case "apikey":
33512
33606
  put2("key", a.apiKeyName);
@@ -33705,6 +33799,10 @@ function readExamples(raw) {
33705
33799
  };
33706
33800
  if (o.contentType) e.contentType = str(o.contentType);
33707
33801
  if (o.savedAt) e.savedAt = str(o.savedAt);
33802
+ if (isObj(o.request) && (o.request.method || o.request.url)) {
33803
+ e.request = { method: str(o.request.method) || "GET", url: str(o.request.url) };
33804
+ if (o.request.body) e.request.body = str(o.request.body);
33805
+ }
33708
33806
  return e;
33709
33807
  });
33710
33808
  }
@@ -33717,6 +33815,7 @@ function writeExamples(list) {
33717
33815
  if (e.contentType) o.contentType = e.contentType;
33718
33816
  if (e.body) o.body = e.body;
33719
33817
  if (e.savedAt) o.savedAt = e.savedAt;
33818
+ if (e.request) o.request = { method: e.request.method, url: e.request.url, ...e.request.body ? { body: e.request.body } : {} };
33720
33819
  return o;
33721
33820
  });
33722
33821
  }
@@ -34202,8 +34301,12 @@ var WorkspaceStore = class _WorkspaceStore {
34202
34301
  async deleteCollection(id) {
34203
34302
  return this.enqueue(async () => {
34204
34303
  const c = this.requireCollection(id);
34205
- if (c.directoryPath) await fs2.rm(c.directoryPath, { recursive: true, force: true });
34304
+ if (c.directoryPath) {
34305
+ await fs2.rm(c.directoryPath, { recursive: true, force: true });
34306
+ this.forget(c.directoryPath);
34307
+ }
34206
34308
  await this.dropLocal(collectionScope(c.id));
34309
+ await this.dropTokens(c);
34207
34310
  this.workspace.collections = this.workspace.collections.filter((x) => x !== c);
34208
34311
  return c;
34209
34312
  });
@@ -34214,6 +34317,7 @@ var WorkspaceStore = class _WorkspaceStore {
34214
34317
  if (!src.directoryPath) throw new Error("Collection has no directory");
34215
34318
  const name = copyName(src.name);
34216
34319
  const dir = uniqueDirectoryPath(this.workspace.collectionsPath, fileSafeName(name, "Collection"));
34320
+ this.forget(dir);
34217
34321
  await fs2.cp(src.directoryPath, dir, { recursive: true });
34218
34322
  const c = structuredClone(src);
34219
34323
  repathTree(c, src.directoryPath, dir);
@@ -34221,7 +34325,7 @@ var WorkspaceStore = class _WorkspaceStore {
34221
34325
  c.name = name;
34222
34326
  c.filePath = path4.join(dir, DEFINITION_FILE);
34223
34327
  c.order = nextOrder(this.workspace.collections);
34224
- await this.writeTree(c);
34328
+ await this.writeCopy(c, dir);
34225
34329
  this.workspace.collections.push(c);
34226
34330
  return c;
34227
34331
  });
@@ -34242,19 +34346,24 @@ var WorkspaceStore = class _WorkspaceStore {
34242
34346
  for (const [i, r] of node.requests.entries()) {
34243
34347
  r.order = (i + 1) * ORDER_STEP;
34244
34348
  r.sourceFilePath = uniqueFilePath(nodeDir, fileSafeName(r.name, "New Request"), REQUEST_SUFFIX);
34245
- await this.writeFile(r.sourceFilePath, requestToYaml(r, void 0, r.sourceFilePath));
34349
+ const text3 = requestToYaml(r, void 0, r.sourceFilePath);
34350
+ await this.writeFile(r.sourceFilePath, text3);
34351
+ r.baseHash = textHash(text3);
34352
+ await this.saveToken(r.id, r.auth);
34246
34353
  }
34247
34354
  for (const [i, f] of node.folders.entries()) {
34248
34355
  f.order = (i + 1) * ORDER_STEP;
34249
34356
  f.directoryPath = uniqueDirectoryPath(nodeDir, fileSafeName(f.name, "New Folder"));
34250
34357
  await fs2.mkdir(f.directoryPath, { recursive: true });
34251
34358
  await this.writeFile(path4.join(f.directoryPath, DEFINITION_FILE), folderToYaml(f, void 0, path4.basename(f.directoryPath)));
34359
+ await this.saveToken(f.id, f.auth);
34252
34360
  await placeAndWrite(f, f.directoryPath);
34253
34361
  }
34254
34362
  };
34255
34363
  await placeAndWrite(c, dir);
34256
34364
  await this.saveLocal(collectionScope(c.id), c.variables);
34257
34365
  await this.writeFile(c.filePath, collectionToYaml(c));
34366
+ await this.saveToken(c.id, c.auth);
34258
34367
  ws.collections.push(c);
34259
34368
  return c;
34260
34369
  });
@@ -34290,7 +34399,11 @@ var WorkspaceStore = class _WorkspaceStore {
34290
34399
  return this.enqueue(async () => {
34291
34400
  const hit = this.locateFolder(id);
34292
34401
  if (!hit) throw new Error(`Folder not found: ${id}`);
34293
- if (hit.folder.directoryPath) await fs2.rm(hit.folder.directoryPath, { recursive: true, force: true });
34402
+ if (hit.folder.directoryPath) {
34403
+ await fs2.rm(hit.folder.directoryPath, { recursive: true, force: true });
34404
+ this.forget(hit.folder.directoryPath);
34405
+ }
34406
+ await this.dropTokens(hit.folder);
34294
34407
  hit.list.splice(hit.list.indexOf(hit.folder), 1);
34295
34408
  return hit.folder;
34296
34409
  });
@@ -34303,12 +34416,13 @@ var WorkspaceStore = class _WorkspaceStore {
34303
34416
  const srcDir = src.directoryPath;
34304
34417
  const name = copyName(src.name);
34305
34418
  const dir = uniqueDirectoryPath(path4.dirname(srcDir), fileSafeName(name, "Folder"));
34419
+ this.forget(dir);
34306
34420
  await fs2.cp(srcDir, dir, { recursive: true });
34307
34421
  const f = structuredClone(src);
34308
34422
  repathTree(f, srcDir, dir);
34309
34423
  freshIds(f);
34310
34424
  f.name = name;
34311
- await this.writeTree(f);
34425
+ await this.writeCopy(f, dir);
34312
34426
  hit.list.splice(hit.list.indexOf(src) + 1, 0, f);
34313
34427
  await this.saveContainerOrder(hit.collection, hit.folders.at(-1) ?? null);
34314
34428
  return f;
@@ -34323,7 +34437,10 @@ var WorkspaceStore = class _WorkspaceStore {
34323
34437
  const id = init?.id && !this.idInUse(init.id) ? init.id : newId();
34324
34438
  const r = newRequest({ ...init, id, name, sourceFilePath: file, order: nextOrder(node.requests) });
34325
34439
  r.method = (r.method || "GET").toUpperCase();
34326
- await this.writeFile(file, requestToYaml(r, void 0, file));
34440
+ const text3 = requestToYaml(r, void 0, file);
34441
+ await this.writeFile(file, text3);
34442
+ r.baseHash = textHash(text3);
34443
+ await this.saveToken(r.id, r.auth);
34327
34444
  node.requests.push(r);
34328
34445
  return r;
34329
34446
  });
@@ -34345,7 +34462,15 @@ var WorkspaceStore = class _WorkspaceStore {
34345
34462
  };
34346
34463
  if (!existing.sourceFilePath) throw new Error("Request has no file path");
34347
34464
  if (!updated.name.trim()) updated.name = existing.name;
34348
- await this.writeRequest(updated, opts.force);
34465
+ let force = opts.force === true;
34466
+ if (!force && request3.baseHash) {
34467
+ const now = await readIfExists(existing.sourceFilePath);
34468
+ if (now !== void 0 && textHash(now) !== request3.baseHash) {
34469
+ throw conflict(`${this.relPath(existing.sourceFilePath)} changed on disk since this request was opened (a git pull or another editor?). Overwrite it, or use the disk version.`, "changed");
34470
+ }
34471
+ force = now !== void 0 && !this.track.broken.has(existing.sourceFilePath);
34472
+ }
34473
+ await this.writeRequest(updated, force);
34349
34474
  hit.list[hit.list.indexOf(existing)] = updated;
34350
34475
  return updated;
34351
34476
  });
@@ -34354,7 +34479,11 @@ var WorkspaceStore = class _WorkspaceStore {
34354
34479
  return this.enqueue(async () => {
34355
34480
  const hit = this.locateRequest(id);
34356
34481
  if (!hit) throw new Error(`Request not found: ${id}`);
34357
- if (hit.request.sourceFilePath) await fs2.rm(hit.request.sourceFilePath, { force: true });
34482
+ if (hit.request.sourceFilePath) {
34483
+ await fs2.rm(hit.request.sourceFilePath, { force: true });
34484
+ this.forget(hit.request.sourceFilePath);
34485
+ }
34486
+ await this.dropTokens(hit.request);
34358
34487
  hit.list.splice(hit.list.indexOf(hit.request), 1);
34359
34488
  return hit.request;
34360
34489
  });
@@ -34369,7 +34498,10 @@ var WorkspaceStore = class _WorkspaceStore {
34369
34498
  for (const e of r.examples ?? []) e.id = newId();
34370
34499
  r.name = copyName(src.name);
34371
34500
  r.sourceFilePath = uniqueFilePath(path4.dirname(src.sourceFilePath), fileSafeName(r.name, "New Request"), REQUEST_SUFFIX);
34372
- await this.writeFile(r.sourceFilePath, requestToYaml(r, await readIfExists(src.sourceFilePath), r.sourceFilePath));
34501
+ const text3 = requestToYaml(r, await readIfExists(src.sourceFilePath), r.sourceFilePath);
34502
+ await this.writeFile(r.sourceFilePath, text3);
34503
+ r.baseHash = textHash(text3);
34504
+ await this.saveToken(r.id, r.auth);
34373
34505
  hit.list.splice(hit.list.indexOf(src) + 1, 0, r);
34374
34506
  await this.saveContainerOrder(hit.collection, hit.folders.at(-1) ?? null);
34375
34507
  return r;
@@ -34435,7 +34567,8 @@ var WorkspaceStore = class _WorkspaceStore {
34435
34567
  const fresh = await this.changedOnDisk(file);
34436
34568
  if (fresh !== void 0) {
34437
34569
  try {
34438
- r = { ...requestFromDto(loadYaml(fresh), file), id: r.id };
34570
+ r = { ...requestFromDto(loadYaml(fresh), file), id: r.id, baseHash: textHash(fresh) };
34571
+ r.auth = withTokensOf(r0.auth, r.auth);
34439
34572
  node.requests[i] = r;
34440
34573
  } catch (e) {
34441
34574
  this.track.broken.set(file, parseMessage(e));
@@ -34453,7 +34586,9 @@ var WorkspaceStore = class _WorkspaceStore {
34453
34586
  const fresh = await this.changedOnDisk(file);
34454
34587
  if (fresh !== void 0) {
34455
34588
  try {
34589
+ const before2 = f.auth;
34456
34590
  applyFolderMetadata(f, loadYaml(fresh));
34591
+ f.auth = withTokensOf(before2, f.auth);
34457
34592
  } catch (e) {
34458
34593
  this.track.broken.set(file, parseMessage(e));
34459
34594
  continue;
@@ -34513,7 +34648,10 @@ var WorkspaceStore = class _WorkspaceStore {
34513
34648
  const list = this.workspace.environments;
34514
34649
  const env = list.find((e) => e.id === id);
34515
34650
  if (!env) throw new Error(`Environment not found: ${id}`);
34516
- if (env.filePath) await fs2.rm(env.filePath, { force: true });
34651
+ if (env.filePath) {
34652
+ await fs2.rm(env.filePath, { force: true });
34653
+ this.forget(env.filePath);
34654
+ }
34517
34655
  await this.dropLocal(environmentScope(env.id));
34518
34656
  list.splice(list.indexOf(env), 1);
34519
34657
  return env;
@@ -34536,13 +34674,28 @@ var WorkspaceStore = class _WorkspaceStore {
34536
34674
  return this.enqueue(async () => {
34537
34675
  const file = path4.join(this.workspace.globalsPath, GLOBALS_FILE);
34538
34676
  const legacy = path4.join(this.workspace.globalsPath, LEGACY_GLOBALS_FILE);
34677
+ await this.assertWritable(legacy);
34539
34678
  await this.saveLocal(GLOBALS_SCOPE, vars);
34540
34679
  await this.writeFile(file, globalsToYaml(vars, await readIfExists(file) ?? await readIfExists(legacy)));
34541
34680
  await fs2.rm(legacy, { force: true });
34681
+ this.track.seen.set(legacy, ABSENT);
34542
34682
  this.workspace.globals = structuredClone(vars);
34543
34683
  return this.workspace.globals;
34544
34684
  });
34545
34685
  }
34686
+ /**
34687
+ * A new OAuth 2.0 token for a collection or folder: kept in memory and the data folder
34688
+ * only, so the YAML (which never holds tokens) isn't rewritten.
34689
+ */
34690
+ async setToken(id, t) {
34691
+ return this.enqueue(async () => {
34692
+ const node = this.findCollection(id) ?? this.locateFolder(id)?.folder;
34693
+ const auth = node?.auth;
34694
+ if (!node || auth?.type !== "oauth2") return;
34695
+ node.auth = { ...auth, oauth2: { ...auth.oauth2, accessToken: t.accessToken, refreshToken: t.refreshToken || auth.oauth2.refreshToken, expiresAt: t.expiresAt } };
34696
+ await this.saveToken(id, node.auth);
34697
+ });
34698
+ }
34546
34699
  // ---- Internals -----------------------------------------------------------
34547
34700
  enqueue(fn) {
34548
34701
  const run2 = this.queue.then(fn, fn);
@@ -34603,6 +34756,10 @@ var WorkspaceStore = class _WorkspaceStore {
34603
34756
  }
34604
34757
  }
34605
34758
  async writeFile(file, content, force = false) {
34759
+ if (!this.track.broken.has(file) && await readIfExists(file) === content) {
34760
+ this.track.seen.set(file, content);
34761
+ return;
34762
+ }
34606
34763
  await this.assertWritable(file, force);
34607
34764
  await writeFileAtomic(file, content);
34608
34765
  this.track.seen.set(file, content);
@@ -34623,6 +34780,28 @@ var WorkspaceStore = class _WorkspaceStore {
34623
34780
  }
34624
34781
  }
34625
34782
  }
34783
+ /** Forgets what was read under a path that was deleted or is about to be filled by a copy. */
34784
+ forget(target) {
34785
+ for (const map2 of [this.track.seen, this.track.broken]) {
34786
+ for (const file of [...map2.keys()]) if (file === target || file.startsWith(target + path4.sep)) map2.delete(file);
34787
+ }
34788
+ }
34789
+ /** Removes the stored tokens of a deleted item and everything under it. */
34790
+ async dropTokens(node) {
34791
+ if (!this.local) return;
34792
+ const ids = [];
34793
+ const walk2 = (n) => {
34794
+ ids.push(n.id);
34795
+ for (const r of n.requests) ids.push(r.id);
34796
+ for (const f of n.folders) walk2(f);
34797
+ };
34798
+ if ("folders" in node) walk2(node);
34799
+ else ids.push(node.id);
34800
+ const tokens = { ...this.local.get(TOKENS_SCOPE) };
34801
+ if (!ids.some((id) => id in tokens)) return;
34802
+ for (const id of ids) delete tokens[id];
34803
+ await this.local.set(TOKENS_SCOPE, tokens);
34804
+ }
34626
34805
  /** The file's text if it changed on disk since Yamlet read it (it is then taken as read). */
34627
34806
  async changedOnDisk(file) {
34628
34807
  const known = this.track.seen.get(file);
@@ -34636,34 +34815,49 @@ var WorkspaceStore = class _WorkspaceStore {
34636
34815
  async writeRequest(r, force = false) {
34637
34816
  const file = r.sourceFilePath;
34638
34817
  if (!file) return;
34639
- await this.assertWritable(file, force);
34640
34818
  const original = await readIfExists(file);
34641
34819
  const target = uniqueFilePath(path4.dirname(file), fileSafeName(r.name, "New Request"), REQUEST_SUFFIX, file);
34642
34820
  if (target !== file && existsSync3(file)) {
34821
+ await this.assertWritable(file, force);
34643
34822
  await fs2.rename(file, target);
34644
34823
  this.moved(file, target);
34645
34824
  }
34646
34825
  r.sourceFilePath = target;
34647
- await this.writeFile(target, requestToYaml(r, original, target), force);
34826
+ const text3 = requestToYaml(r, original, target);
34827
+ await this.writeFile(target, text3, force);
34828
+ r.baseHash = textHash(text3);
34829
+ await this.saveToken(r.id, r.auth);
34830
+ }
34831
+ /** Keeps an item's OAuth 2.0 token (and expiry) in the data folder; the YAML never has it. */
34832
+ async saveToken(id, auth) {
34833
+ if (!this.local) return;
34834
+ const tokens = { ...this.local.get(TOKENS_SCOPE) };
34835
+ const o = auth?.type === "oauth2" ? auth.oauth2 : void 0;
34836
+ const entry = o && (o.accessToken || o.refreshToken) ? JSON.stringify({ accessToken: o.accessToken, refreshToken: o.refreshToken, ...o.expiresAt ? { expiresAt: o.expiresAt } : {} }) : void 0;
34837
+ if (tokens[id] === entry) return;
34838
+ if (entry) tokens[id] = entry;
34839
+ else delete tokens[id];
34840
+ await this.local.set(TOKENS_SCOPE, tokens);
34648
34841
  }
34649
34842
  /** Writes `.resources/definition.yaml` (renaming the directory to match the name) and drops a legacy collection.yaml. */
34650
34843
  async writeCollection(c) {
34651
34844
  if (!c.directoryPath) throw new Error("Collection has no directory");
34652
- await this.assertWritable(path4.join(c.directoryPath, DEFINITION_FILE));
34845
+ if (path4.basename(c.directoryPath) !== fileSafeName(c.name, "New Collection")) await this.assertWritable(path4.join(c.directoryPath, DEFINITION_FILE));
34653
34846
  await this.renameNodeDir(c, this.workspace.collectionsPath, fileSafeName(c.name, "New Collection"));
34654
34847
  const file = path4.join(c.directoryPath, DEFINITION_FILE);
34655
34848
  const legacy = path4.join(c.directoryPath, LEGACY_COLLECTION_FILE);
34656
- await this.assertWritable(file);
34657
34849
  await this.assertWritable(legacy);
34658
34850
  const original = await readIfExists(file) ?? await readIfExists(legacy);
34659
34851
  c.filePath = file;
34660
34852
  await this.saveLocal(collectionScope(c.id), c.variables);
34661
34853
  await this.writeFile(file, collectionToYaml(c, original));
34854
+ await this.saveToken(c.id, c.auth);
34662
34855
  await fs2.rm(legacy, { force: true });
34856
+ this.forget(legacy);
34663
34857
  }
34664
34858
  async writeFolder(f) {
34665
34859
  if (!f.directoryPath) return;
34666
- await this.assertWritable(path4.join(f.directoryPath, DEFINITION_FILE));
34860
+ if (path4.basename(f.directoryPath) !== fileSafeName(f.name, "New Folder")) await this.assertWritable(path4.join(f.directoryPath, DEFINITION_FILE));
34667
34861
  await this.assertWritable(path4.join(f.directoryPath, LEGACY_FOLDER_FILE));
34668
34862
  await this.renameNodeDir(f, path4.dirname(f.directoryPath), fileSafeName(f.name, "New Folder"));
34669
34863
  const dir = f.directoryPath;
@@ -34671,7 +34865,19 @@ var WorkspaceStore = class _WorkspaceStore {
34671
34865
  const legacy = path4.join(dir, LEGACY_FOLDER_FILE);
34672
34866
  const original = await readIfExists(file) ?? await readIfExists(legacy);
34673
34867
  await this.writeFile(file, folderToYaml(f, original, path4.basename(dir)));
34868
+ await this.saveToken(f.id, f.auth);
34674
34869
  await fs2.rm(legacy, { force: true });
34870
+ this.forget(legacy);
34871
+ }
34872
+ /** Writes a freshly copied tree; if that fails, the copy is removed rather than left behind. */
34873
+ async writeCopy(node, dir) {
34874
+ try {
34875
+ await this.writeTree(node);
34876
+ } catch (e) {
34877
+ await fs2.rm(dir, { recursive: true, force: true }).catch(() => void 0);
34878
+ this.forget(dir);
34879
+ throw e;
34880
+ }
34675
34881
  }
34676
34882
  /** Rewrites every metadata and request file under a node (used after copying a directory). */
34677
34883
  async writeTree(node) {
@@ -34681,6 +34887,13 @@ var WorkspaceStore = class _WorkspaceStore {
34681
34887
  for (const f of node.folders) await this.writeTree(f);
34682
34888
  }
34683
34889
  };
34890
+ function withTokensOf(prev2, next2) {
34891
+ if (prev2?.type !== "oauth2" || next2?.type !== "oauth2") return next2;
34892
+ const { accessToken, refreshToken, expiresAt } = prev2.oauth2;
34893
+ return { ...next2, oauth2: { ...next2.oauth2, accessToken, refreshToken, ...expiresAt ? { expiresAt } : {} } };
34894
+ }
34895
+ var ABSENT = "\0absent";
34896
+ var textHash = (text3) => createHash2("sha256").update(text3).digest("hex").slice(0, 32);
34684
34897
  function conflict(message, reason) {
34685
34898
  return Object.assign(new Error(message), { statusCode: 409, reason });
34686
34899
  }
@@ -34726,6 +34939,10 @@ async function loadWorkspace(root2, track) {
34726
34939
  const current = await readIfExists(globalsFile);
34727
34940
  const globalsText = current ?? await readIfExists(legacyGlobals);
34728
34941
  const globalsPath = current !== void 0 ? globalsFile : legacyGlobals;
34942
+ if (current === void 0) track.seen.set(globalsFile, ABSENT);
34943
+ if (globalsText === void 0 || current !== void 0) {
34944
+ if (!await readIfExists(legacyGlobals)) track.seen.set(legacyGlobals, ABSENT);
34945
+ }
34729
34946
  if (globalsText) {
34730
34947
  try {
34731
34948
  ws.globals = globalsFromYaml(globalsText);
@@ -34749,6 +34966,7 @@ async function loadCollection(dir, rel, track) {
34749
34966
  });
34750
34967
  const read = async (file, apply) => {
34751
34968
  const text3 = await readIfExists(file);
34969
+ if (text3 === void 0) track.seen.set(file, ABSENT);
34752
34970
  if (!text3) return;
34753
34971
  try {
34754
34972
  apply(loadYaml(text3));
@@ -34771,6 +34989,7 @@ async function loadFolders(parentDir, rel, track) {
34771
34989
  const f = newFolder({ id: stableId("f", rel(dir)), name: entry.name, directoryPath: dir });
34772
34990
  for (const file of [DEFINITION_FILE, LEGACY_FOLDER_FILE]) {
34773
34991
  const meta = await readIfExists(path4.join(dir, file));
34992
+ if (meta === void 0) track.seen.set(path4.join(dir, file), ABSENT);
34774
34993
  if (!meta) continue;
34775
34994
  try {
34776
34995
  applyFolderMetadata(f, loadYaml(meta));
@@ -34799,6 +35018,7 @@ async function loadRequests(dir, rel, track) {
34799
35018
  if (raw.$kind !== void 0 && String(raw.$kind) !== REQUEST_KIND) continue;
34800
35019
  const r = requestFromDto(raw, file);
34801
35020
  if (!String(raw.id ?? "").trim()) r.id = stableId("r", rel(file));
35021
+ r.baseHash = textHash(text3);
34802
35022
  requests.push(r);
34803
35023
  track.seen.set(file, text3);
34804
35024
  } catch (e) {
@@ -34845,6 +35065,22 @@ function applyLocalValues(ws, local) {
34845
35065
  for (const e of ws.environments) overlay(e.variables, environmentScope(e.id));
34846
35066
  for (const c of ws.collections) overlay(c.variables, collectionScope(c.id));
34847
35067
  overlay(ws.globals, GLOBALS_SCOPE);
35068
+ const tokens = local.get(TOKENS_SCOPE);
35069
+ if (!tokens) return;
35070
+ const withToken = (id, auth) => {
35071
+ if (auth?.type !== "oauth2" || !tokens[id]) return;
35072
+ try {
35073
+ const t = JSON.parse(tokens[id]);
35074
+ auth.oauth2 = { ...auth.oauth2, accessToken: t.accessToken ?? "", refreshToken: t.refreshToken ?? "", ...t.expiresAt ? { expiresAt: t.expiresAt } : {} };
35075
+ } catch {
35076
+ }
35077
+ };
35078
+ const walk2 = (node) => {
35079
+ withToken(node.id, node.auth);
35080
+ for (const r of node.requests) withToken(r.id, r.auth);
35081
+ for (const f of node.folders) walk2(f);
35082
+ };
35083
+ ws.collections.forEach(walk2);
34848
35084
  }
34849
35085
 
34850
35086
  // core/src/runReports.ts
@@ -34880,7 +35116,7 @@ function formatJson(summary) {
34880
35116
  }
34881
35117
 
34882
35118
  // cli/src/index.ts
34883
- var VERSION = true ? "1.14.0" : "0.0.0-dev";
35119
+ var VERSION = true ? "1.15.0" : "0.0.0-dev";
34884
35120
  setYamletVersion(VERSION);
34885
35121
  var CliError = class extends Error {
34886
35122
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piyushdoorwar/yamlet",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "Run Yamlet API collections (Git-friendly YAML) from the command line or CI.",
5
5
  "type": "module",
6
6
  "bin": {