norn-cli 1.5.3 → 1.6.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.
package/dist/cli.js CHANGED
@@ -8881,11 +8881,11 @@ var require_mime_types = __commonJS({
8881
8881
  }
8882
8882
  return exts[0];
8883
8883
  }
8884
- function lookup(path9) {
8885
- if (!path9 || typeof path9 !== "string") {
8884
+ function lookup(path10) {
8885
+ if (!path10 || typeof path10 !== "string") {
8886
8886
  return false;
8887
8887
  }
8888
- var extension2 = extname2("x." + path9).toLowerCase().substr(1);
8888
+ var extension2 = extname2("x." + path10).toLowerCase().substr(1);
8889
8889
  if (!extension2) {
8890
8890
  return false;
8891
8891
  }
@@ -9990,11 +9990,11 @@ var require_form_data = __commonJS({
9990
9990
  "use strict";
9991
9991
  var CombinedStream = require_combined_stream();
9992
9992
  var util3 = require("util");
9993
- var path9 = require("path");
9993
+ var path10 = require("path");
9994
9994
  var http3 = require("http");
9995
9995
  var https3 = require("https");
9996
9996
  var parseUrl = require("url").parse;
9997
- var fs10 = require("fs");
9997
+ var fs11 = require("fs");
9998
9998
  var Stream = require("stream").Stream;
9999
9999
  var crypto3 = require("crypto");
10000
10000
  var mime = require_mime_types();
@@ -10061,7 +10061,7 @@ var require_form_data = __commonJS({
10061
10061
  if (value.end != void 0 && value.end != Infinity && value.start != void 0) {
10062
10062
  callback(null, value.end + 1 - (value.start ? value.start : 0));
10063
10063
  } else {
10064
- fs10.stat(value.path, function(err, stat) {
10064
+ fs11.stat(value.path, function(err, stat) {
10065
10065
  if (err) {
10066
10066
  callback(err);
10067
10067
  return;
@@ -10118,11 +10118,11 @@ var require_form_data = __commonJS({
10118
10118
  FormData3.prototype._getContentDisposition = function(value, options) {
10119
10119
  var filename;
10120
10120
  if (typeof options.filepath === "string") {
10121
- filename = path9.normalize(options.filepath).replace(/\\/g, "/");
10121
+ filename = path10.normalize(options.filepath).replace(/\\/g, "/");
10122
10122
  } else if (options.filename || value && (value.name || value.path)) {
10123
- filename = path9.basename(options.filename || value && (value.name || value.path));
10123
+ filename = path10.basename(options.filename || value && (value.name || value.path));
10124
10124
  } else if (value && value.readable && hasOwn(value, "httpVersion")) {
10125
- filename = path9.basename(value.client._httpMessage.path || "");
10125
+ filename = path10.basename(value.client._httpMessage.path || "");
10126
10126
  }
10127
10127
  if (filename) {
10128
10128
  return 'filename="' + filename + '"';
@@ -15331,8 +15331,8 @@ var require_utils = __commonJS({
15331
15331
  }
15332
15332
  return ind;
15333
15333
  }
15334
- function removeDotSegments(path9) {
15335
- let input2 = path9;
15334
+ function removeDotSegments(path10) {
15335
+ let input2 = path10;
15336
15336
  const output2 = [];
15337
15337
  let nextSlash = -1;
15338
15338
  let len = 0;
@@ -15531,8 +15531,8 @@ var require_schemes = __commonJS({
15531
15531
  wsComponent.secure = void 0;
15532
15532
  }
15533
15533
  if (wsComponent.resourceName) {
15534
- const [path9, query] = wsComponent.resourceName.split("?");
15535
- wsComponent.path = path9 && path9 !== "/" ? path9 : void 0;
15534
+ const [path10, query] = wsComponent.resourceName.split("?");
15535
+ wsComponent.path = path10 && path10 !== "/" ? path10 : void 0;
15536
15536
  wsComponent.query = query;
15537
15537
  wsComponent.resourceName = void 0;
15538
15538
  }
@@ -18738,8 +18738,8 @@ function validateAgainstSchemaDetailed(value, schemaPath, basePath, workspaceRoo
18738
18738
  if (!valid && validate2.errors) {
18739
18739
  const errors = validate2.errors.map((err) => convertAjvError(err, value));
18740
18740
  const errorStrings = validate2.errors.map((err) => {
18741
- const path9 = err.instancePath || "(root)";
18742
- return `${path9}: ${err.message}`;
18741
+ const path10 = err.instancePath || "(root)";
18742
+ return `${path10}: ${err.message}`;
18743
18743
  });
18744
18744
  return {
18745
18745
  valid: false,
@@ -18868,7 +18868,7 @@ function resolveValue(expr, responses, variables, getValueByPath2, responseIndex
18868
18868
  if (refMatch) {
18869
18869
  const responseIdx = parseInt(refMatch[1], 10);
18870
18870
  const responseIndex = responseIdx - 1;
18871
- const path9 = refMatch[2];
18871
+ const path10 = refMatch[2];
18872
18872
  if (responseIndex < 0 || responseIndex >= responses.length) {
18873
18873
  return {
18874
18874
  value: void 0,
@@ -18876,12 +18876,12 @@ function resolveValue(expr, responses, variables, getValueByPath2, responseIndex
18876
18876
  };
18877
18877
  }
18878
18878
  const response = responses[responseIndex];
18879
- const value = getValueByPath2(response, path9);
18879
+ const value = getValueByPath2(response, path10);
18880
18880
  return {
18881
18881
  value,
18882
18882
  responseIndex: responseIdx,
18883
18883
  response,
18884
- jsonPath: path9,
18884
+ jsonPath: path10,
18885
18885
  variableName: responseIndexToVariable?.get(responseIdx)
18886
18886
  };
18887
18887
  }
@@ -18898,22 +18898,22 @@ function resolveValue(expr, responses, variables, getValueByPath2, responseIndex
18898
18898
  if (varName in variables) {
18899
18899
  const varValue = variables[varName];
18900
18900
  if (typeof varValue === "object" && varValue !== null) {
18901
- const path9 = pathPart.replace(/^\./, "");
18902
- const value = getNestedValue2(varValue, path9);
18901
+ const path10 = pathPart.replace(/^\./, "");
18902
+ const value = getNestedValue2(varValue, path10);
18903
18903
  const isHttpResponse = "status" in varValue && "body" in varValue;
18904
18904
  return {
18905
18905
  value,
18906
18906
  variableName: varName,
18907
- jsonPath: path9,
18907
+ jsonPath: path10,
18908
18908
  response: isHttpResponse ? varValue : void 0
18909
18909
  };
18910
18910
  }
18911
18911
  if (typeof varValue === "string") {
18912
18912
  try {
18913
18913
  const parsed = JSON.parse(varValue);
18914
- const path9 = pathPart.replace(/^\./, "");
18915
- const value = getNestedValue2(parsed, path9);
18916
- return { value, variableName: varName, jsonPath: path9 };
18914
+ const path10 = pathPart.replace(/^\./, "");
18915
+ const value = getNestedValue2(parsed, path10);
18916
+ return { value, variableName: varName, jsonPath: path10 };
18917
18917
  } catch {
18918
18918
  return { value: void 0, error: `Cannot access path on non-object variable: ${varName}` };
18919
18919
  }
@@ -18969,11 +18969,11 @@ function resolveValue(expr, responses, variables, getValueByPath2, responseIndex
18969
18969
  }
18970
18970
  return { value: void 0, error: `Cannot resolve expression: ${trimmed}` };
18971
18971
  }
18972
- function getNestedValue2(obj, path9) {
18973
- if (!path9 || obj === null || obj === void 0) {
18972
+ function getNestedValue2(obj, path10) {
18973
+ if (!path10 || obj === null || obj === void 0) {
18974
18974
  return obj;
18975
18975
  }
18976
- const parts = path9.replace(/\[(\d+)\]/g, ".$1").split(".").filter((p) => p !== "");
18976
+ const parts = path10.replace(/\[(\d+)\]/g, ".$1").split(".").filter((p) => p !== "");
18977
18977
  let current = obj;
18978
18978
  for (const part of parts) {
18979
18979
  if (current === null || current === void 0) {
@@ -19769,16 +19769,16 @@ var init_source = __esm({
19769
19769
  });
19770
19770
 
19771
19771
  // src/cli.ts
19772
- var fs9 = __toESM(require("fs"));
19772
+ var fs10 = __toESM(require("fs"));
19773
19773
  var fsPromises = __toESM(require("fs/promises"));
19774
- var path8 = __toESM(require("path"));
19774
+ var path9 = __toESM(require("path"));
19775
19775
 
19776
19776
  // src/nornapiParser.ts
19777
- function extractPathParameters(path9) {
19777
+ function extractPathParameters(path10) {
19778
19778
  const params = [];
19779
19779
  const regex = /(?<!\{)\{([a-zA-Z_][a-zA-Z0-9_]*)\}(?!\})/g;
19780
19780
  let match;
19781
- while ((match = regex.exec(path9)) !== null) {
19781
+ while ((match = regex.exec(path10)) !== null) {
19782
19782
  params.push(match[1]);
19783
19783
  }
19784
19784
  return params;
@@ -19828,12 +19828,12 @@ function parseNornApiFile(content) {
19828
19828
  if (inEndpointsBlock) {
19829
19829
  const endpointMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(.+)$/i);
19830
19830
  if (endpointMatch) {
19831
- const path9 = endpointMatch[3].trim();
19831
+ const path10 = endpointMatch[3].trim();
19832
19832
  endpoints.push({
19833
19833
  name: endpointMatch[1],
19834
19834
  method: endpointMatch[2].toUpperCase(),
19835
- path: path9,
19836
- parameters: extractPathParameters(path9)
19835
+ path: path10,
19836
+ parameters: extractPathParameters(path10)
19837
19837
  });
19838
19838
  }
19839
19839
  continue;
@@ -20123,11 +20123,11 @@ function extractFileLevelVariables(text) {
20123
20123
  }
20124
20124
  return variables;
20125
20125
  }
20126
- function getNestedValue(obj, path9) {
20127
- if (!path9 || obj === null || obj === void 0) {
20126
+ function getNestedValue(obj, path10) {
20127
+ if (!path10 || obj === null || obj === void 0) {
20128
20128
  return obj;
20129
20129
  }
20130
- const parts = path9.replace(/\[(\d+)\]/g, ".$1").split(".").filter((p) => p !== "");
20130
+ const parts = path10.replace(/\[(\d+)\]/g, ".$1").split(".").filter((p) => p !== "");
20131
20131
  let current = obj;
20132
20132
  for (const part of parts) {
20133
20133
  if (current === null || current === void 0) {
@@ -20159,8 +20159,8 @@ function substituteVariables(text, variables) {
20159
20159
  const value = variables[varName];
20160
20160
  if (typeof value === "object" && value !== null) {
20161
20161
  if (pathPart) {
20162
- const path9 = pathPart.replace(/^\./, "");
20163
- const nestedValue = getNestedValue(value, path9);
20162
+ const path10 = pathPart.replace(/^\./, "");
20163
+ const nestedValue = getNestedValue(value, path10);
20164
20164
  return valueToString(nestedValue);
20165
20165
  }
20166
20166
  return valueToString(value);
@@ -20168,8 +20168,8 @@ function substituteVariables(text, variables) {
20168
20168
  if (pathPart && typeof value === "string") {
20169
20169
  try {
20170
20170
  const parsed = JSON.parse(value);
20171
- const path9 = pathPart.replace(/^\./, "");
20172
- const nestedValue = getNestedValue(parsed, path9);
20171
+ const path10 = pathPart.replace(/^\./, "");
20172
+ const nestedValue = getNestedValue(parsed, path10);
20173
20173
  return valueToString(nestedValue);
20174
20174
  } catch {
20175
20175
  return value;
@@ -20285,8 +20285,8 @@ async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__
20285
20285
  const namedRequestSources = /* @__PURE__ */ new Map();
20286
20286
  const sequenceSources = /* @__PURE__ */ new Map();
20287
20287
  for (const imp of imports) {
20288
- const path9 = await import("path");
20289
- const absolutePath = path9.resolve(baseDir, imp.path);
20288
+ const path10 = await import("path");
20289
+ const absolutePath = path10.resolve(baseDir, imp.path);
20290
20290
  if (importStack.has(absolutePath)) {
20291
20291
  errors.push({
20292
20292
  path: imp.path,
@@ -20333,7 +20333,7 @@ async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__
20333
20333
  }
20334
20334
  continue;
20335
20335
  }
20336
- const importDir = path9.dirname(absolutePath);
20336
+ const importDir = path10.dirname(absolutePath);
20337
20337
  const nestedResult = await resolveImports(content, importDir, readFile2, alreadyImported, importStack);
20338
20338
  errors.push(...nestedResult.errors);
20339
20339
  resolvedPaths.push(...nestedResult.resolvedPaths);
@@ -20932,9 +20932,9 @@ function isVisitable(thing) {
20932
20932
  function removeBrackets(key) {
20933
20933
  return utils_default.endsWith(key, "[]") ? key.slice(0, -2) : key;
20934
20934
  }
20935
- function renderKey(path9, key, dots) {
20936
- if (!path9) return key;
20937
- return path9.concat(key).map(function each(token, i) {
20935
+ function renderKey(path10, key, dots) {
20936
+ if (!path10) return key;
20937
+ return path10.concat(key).map(function each(token, i) {
20938
20938
  token = removeBrackets(token);
20939
20939
  return !dots && i ? "[" + token + "]" : token;
20940
20940
  }).join(dots ? "." : "");
@@ -20982,9 +20982,9 @@ function toFormData(obj, formData, options) {
20982
20982
  }
20983
20983
  return value;
20984
20984
  }
20985
- function defaultVisitor(value, key, path9) {
20985
+ function defaultVisitor(value, key, path10) {
20986
20986
  let arr = value;
20987
- if (value && !path9 && typeof value === "object") {
20987
+ if (value && !path10 && typeof value === "object") {
20988
20988
  if (utils_default.endsWith(key, "{}")) {
20989
20989
  key = metaTokens ? key : key.slice(0, -2);
20990
20990
  value = JSON.stringify(value);
@@ -21003,7 +21003,7 @@ function toFormData(obj, formData, options) {
21003
21003
  if (isVisitable(value)) {
21004
21004
  return true;
21005
21005
  }
21006
- formData.append(renderKey(path9, key, dots), convertValue(value));
21006
+ formData.append(renderKey(path10, key, dots), convertValue(value));
21007
21007
  return false;
21008
21008
  }
21009
21009
  const stack = [];
@@ -21012,10 +21012,10 @@ function toFormData(obj, formData, options) {
21012
21012
  convertValue,
21013
21013
  isVisitable
21014
21014
  });
21015
- function build(value, path9) {
21015
+ function build(value, path10) {
21016
21016
  if (utils_default.isUndefined(value)) return;
21017
21017
  if (stack.indexOf(value) !== -1) {
21018
- throw Error("Circular reference detected in " + path9.join("."));
21018
+ throw Error("Circular reference detected in " + path10.join("."));
21019
21019
  }
21020
21020
  stack.push(value);
21021
21021
  utils_default.forEach(value, function each(el, key) {
@@ -21023,11 +21023,11 @@ function toFormData(obj, formData, options) {
21023
21023
  formData,
21024
21024
  el,
21025
21025
  utils_default.isString(key) ? key.trim() : key,
21026
- path9,
21026
+ path10,
21027
21027
  exposedHelpers
21028
21028
  );
21029
21029
  if (result === true) {
21030
- build(el, path9 ? path9.concat(key) : [key]);
21030
+ build(el, path10 ? path10.concat(key) : [key]);
21031
21031
  }
21032
21032
  });
21033
21033
  stack.pop();
@@ -21239,7 +21239,7 @@ var platform_default = {
21239
21239
  // node_modules/axios/lib/helpers/toURLEncodedForm.js
21240
21240
  function toURLEncodedForm(data, options) {
21241
21241
  return toFormData_default(data, new platform_default.classes.URLSearchParams(), {
21242
- visitor: function(value, key, path9, helpers) {
21242
+ visitor: function(value, key, path10, helpers) {
21243
21243
  if (platform_default.isNode && utils_default.isBuffer(value)) {
21244
21244
  this.append(key, value.toString("base64"));
21245
21245
  return false;
@@ -21269,11 +21269,11 @@ function arrayToObject(arr) {
21269
21269
  return obj;
21270
21270
  }
21271
21271
  function formDataToJSON(formData) {
21272
- function buildPath(path9, value, target, index) {
21273
- let name = path9[index++];
21272
+ function buildPath(path10, value, target, index) {
21273
+ let name = path10[index++];
21274
21274
  if (name === "__proto__") return true;
21275
21275
  const isNumericKey = Number.isFinite(+name);
21276
- const isLast = index >= path9.length;
21276
+ const isLast = index >= path10.length;
21277
21277
  name = !name && utils_default.isArray(target) ? target.length : name;
21278
21278
  if (isLast) {
21279
21279
  if (utils_default.hasOwnProp(target, name)) {
@@ -21286,7 +21286,7 @@ function formDataToJSON(formData) {
21286
21286
  if (!target[name] || !utils_default.isObject(target[name])) {
21287
21287
  target[name] = [];
21288
21288
  }
21289
- const result = buildPath(path9, value, target[name], index);
21289
+ const result = buildPath(path10, value, target[name], index);
21290
21290
  if (result && utils_default.isArray(target[name])) {
21291
21291
  target[name] = arrayToObject(target[name]);
21292
21292
  }
@@ -22608,9 +22608,9 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config) {
22608
22608
  auth = urlUsername + ":" + urlPassword;
22609
22609
  }
22610
22610
  auth && headers.delete("authorization");
22611
- let path9;
22611
+ let path10;
22612
22612
  try {
22613
- path9 = buildURL(
22613
+ path10 = buildURL(
22614
22614
  parsed.pathname + parsed.search,
22615
22615
  config.params,
22616
22616
  config.paramsSerializer
@@ -22628,7 +22628,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config) {
22628
22628
  false
22629
22629
  );
22630
22630
  const options = {
22631
- path: path9,
22631
+ path: path10,
22632
22632
  method,
22633
22633
  headers: headers.toJSON(),
22634
22634
  agents: { http: config.httpAgent, https: config.httpsAgent },
@@ -22864,14 +22864,14 @@ var isURLSameOrigin_default = platform_default.hasStandardBrowserEnv ? /* @__PUR
22864
22864
  var cookies_default = platform_default.hasStandardBrowserEnv ? (
22865
22865
  // Standard browser envs support document.cookie
22866
22866
  {
22867
- write(name, value, expires, path9, domain, secure, sameSite) {
22867
+ write(name, value, expires, path10, domain, secure, sameSite) {
22868
22868
  if (typeof document === "undefined") return;
22869
22869
  const cookie = [`${name}=${encodeURIComponent(value)}`];
22870
22870
  if (utils_default.isNumber(expires)) {
22871
22871
  cookie.push(`expires=${new Date(expires).toUTCString()}`);
22872
22872
  }
22873
- if (utils_default.isString(path9)) {
22874
- cookie.push(`path=${path9}`);
22873
+ if (utils_default.isString(path10)) {
22874
+ cookie.push(`path=${path10}`);
22875
22875
  }
22876
22876
  if (utils_default.isString(domain)) {
22877
22877
  cookie.push(`domain=${domain}`);
@@ -24271,18 +24271,18 @@ var MemoryCookieStore = class extends Store {
24271
24271
  /**
24272
24272
  * @internal No doc because this is an overload that supports the implementation
24273
24273
  */
24274
- findCookie(domain, path9, key, callback) {
24274
+ findCookie(domain, path10, key, callback) {
24275
24275
  const promiseCallback = createPromiseCallback(callback);
24276
- if (domain == null || path9 == null || key == null) {
24276
+ if (domain == null || path10 == null || key == null) {
24277
24277
  return promiseCallback.resolve(void 0);
24278
24278
  }
24279
- const result = this.idx[domain]?.[path9]?.[key];
24279
+ const result = this.idx[domain]?.[path10]?.[key];
24280
24280
  return promiseCallback.resolve(result);
24281
24281
  }
24282
24282
  /**
24283
24283
  * @internal No doc because this is an overload that supports the implementation
24284
24284
  */
24285
- findCookies(domain, path9, allowSpecialUseDomain = false, callback) {
24285
+ findCookies(domain, path10, allowSpecialUseDomain = false, callback) {
24286
24286
  if (typeof allowSpecialUseDomain === "function") {
24287
24287
  callback = allowSpecialUseDomain;
24288
24288
  allowSpecialUseDomain = true;
@@ -24293,7 +24293,7 @@ var MemoryCookieStore = class extends Store {
24293
24293
  return promiseCallback.resolve([]);
24294
24294
  }
24295
24295
  let pathMatcher;
24296
- if (!path9) {
24296
+ if (!path10) {
24297
24297
  pathMatcher = function matchAll2(domainIndex) {
24298
24298
  for (const curPath in domainIndex) {
24299
24299
  const pathIndex = domainIndex[curPath];
@@ -24308,7 +24308,7 @@ var MemoryCookieStore = class extends Store {
24308
24308
  } else {
24309
24309
  pathMatcher = function matchRFC(domainIndex) {
24310
24310
  for (const cookiePath in domainIndex) {
24311
- if (pathMatch(path9, cookiePath)) {
24311
+ if (pathMatch(path10, cookiePath)) {
24312
24312
  const pathIndex = domainIndex[cookiePath];
24313
24313
  for (const key in pathIndex) {
24314
24314
  const value = pathIndex[key];
@@ -24336,14 +24336,14 @@ var MemoryCookieStore = class extends Store {
24336
24336
  */
24337
24337
  putCookie(cookie, callback) {
24338
24338
  const promiseCallback = createPromiseCallback(callback);
24339
- const { domain, path: path9, key } = cookie;
24340
- if (domain == null || path9 == null || key == null) {
24339
+ const { domain, path: path10, key } = cookie;
24340
+ if (domain == null || path10 == null || key == null) {
24341
24341
  return promiseCallback.resolve(void 0);
24342
24342
  }
24343
24343
  const domainEntry = this.idx[domain] ?? /* @__PURE__ */ Object.create(null);
24344
24344
  this.idx[domain] = domainEntry;
24345
- const pathEntry = domainEntry[path9] ?? /* @__PURE__ */ Object.create(null);
24346
- domainEntry[path9] = pathEntry;
24345
+ const pathEntry = domainEntry[path10] ?? /* @__PURE__ */ Object.create(null);
24346
+ domainEntry[path10] = pathEntry;
24347
24347
  pathEntry[key] = cookie;
24348
24348
  return promiseCallback.resolve(void 0);
24349
24349
  }
@@ -24357,20 +24357,20 @@ var MemoryCookieStore = class extends Store {
24357
24357
  /**
24358
24358
  * @internal No doc because this is an overload that supports the implementation
24359
24359
  */
24360
- removeCookie(domain, path9, key, callback) {
24360
+ removeCookie(domain, path10, key, callback) {
24361
24361
  const promiseCallback = createPromiseCallback(callback);
24362
- delete this.idx[domain]?.[path9]?.[key];
24362
+ delete this.idx[domain]?.[path10]?.[key];
24363
24363
  return promiseCallback.resolve(void 0);
24364
24364
  }
24365
24365
  /**
24366
24366
  * @internal No doc because this is an overload that supports the implementation
24367
24367
  */
24368
- removeCookies(domain, path9, callback) {
24368
+ removeCookies(domain, path10, callback) {
24369
24369
  const promiseCallback = createPromiseCallback(callback);
24370
24370
  const domainEntry = this.idx[domain];
24371
24371
  if (domainEntry) {
24372
- if (path9) {
24373
- delete domainEntry[path9];
24372
+ if (path10) {
24373
+ delete domainEntry[path10];
24374
24374
  } else {
24375
24375
  delete this.idx[domain];
24376
24376
  }
@@ -24396,8 +24396,8 @@ var MemoryCookieStore = class extends Store {
24396
24396
  domains.forEach((domain) => {
24397
24397
  const domainEntry = idx[domain] ?? {};
24398
24398
  const paths = Object.keys(domainEntry);
24399
- paths.forEach((path9) => {
24400
- const pathEntry = domainEntry[path9] ?? {};
24399
+ paths.forEach((path10) => {
24400
+ const pathEntry = domainEntry[path10] ?? {};
24401
24401
  const keys = Object.keys(pathEntry);
24402
24402
  keys.forEach((key) => {
24403
24403
  const keyEntry = pathEntry[key];
@@ -25281,18 +25281,18 @@ function cookieCompare(a, b) {
25281
25281
  cmp = (a.creationIndex || 0) - (b.creationIndex || 0);
25282
25282
  return cmp;
25283
25283
  }
25284
- function defaultPath(path9) {
25285
- if (!path9 || path9.slice(0, 1) !== "/") {
25284
+ function defaultPath(path10) {
25285
+ if (!path10 || path10.slice(0, 1) !== "/") {
25286
25286
  return "/";
25287
25287
  }
25288
- if (path9 === "/") {
25289
- return path9;
25288
+ if (path10 === "/") {
25289
+ return path10;
25290
25290
  }
25291
- const rightSlash = path9.lastIndexOf("/");
25291
+ const rightSlash = path10.lastIndexOf("/");
25292
25292
  if (rightSlash === 0) {
25293
25293
  return "/";
25294
25294
  }
25295
- return path9.slice(0, rightSlash);
25295
+ return path10.slice(0, rightSlash);
25296
25296
  }
25297
25297
  var IP_REGEX_LOWERCASE = /(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/;
25298
25298
  function domainMatch(domain, cookieDomain, canonicalize) {
@@ -25694,7 +25694,7 @@ var CookieJar = class _CookieJar {
25694
25694
  return promiseCallback.reject(parameterError);
25695
25695
  }
25696
25696
  const host = canonicalDomain(context.hostname);
25697
- const path9 = context.pathname || "/";
25697
+ const path10 = context.pathname || "/";
25698
25698
  const potentiallyTrustworthy = isPotentiallyTrustworthy(
25699
25699
  url2,
25700
25700
  this.allowSecureOnLocal
@@ -25725,7 +25725,7 @@ var CookieJar = class _CookieJar {
25725
25725
  return false;
25726
25726
  }
25727
25727
  }
25728
- if (!allPaths && typeof c.path === "string" && !pathMatch(path9, c.path)) {
25728
+ if (!allPaths && typeof c.path === "string" && !pathMatch(path10, c.path)) {
25729
25729
  return false;
25730
25730
  }
25731
25731
  if (c.secure && !potentiallyTrustworthy) {
@@ -25757,7 +25757,7 @@ var CookieJar = class _CookieJar {
25757
25757
  }
25758
25758
  store.findCookies(
25759
25759
  host,
25760
- allPaths ? null : path9,
25760
+ allPaths ? null : path10,
25761
25761
  this.allowSpecialUseDomain,
25762
25762
  (err, cookies) => {
25763
25763
  if (err) {
@@ -26785,11 +26785,11 @@ function readJsonFile(filePath, workingDir) {
26785
26785
  };
26786
26786
  }
26787
26787
  }
26788
- function setNestedValue(obj, path9, value) {
26789
- if (!path9 || obj === null || obj === void 0 || typeof obj !== "object") {
26788
+ function setNestedValue(obj, path10, value) {
26789
+ if (!path10 || obj === null || obj === void 0 || typeof obj !== "object") {
26790
26790
  return false;
26791
26791
  }
26792
- const parts = path9.replace(/\[(\d+)\]/g, ".$1").split(".").filter((p) => p !== "");
26792
+ const parts = path10.replace(/\[(\d+)\]/g, ".$1").split(".").filter((p) => p !== "");
26793
26793
  if (parts.length === 0) {
26794
26794
  return false;
26795
26795
  }
@@ -27291,8 +27291,8 @@ function bindSequenceArguments(params, args, runtimeVariables) {
27291
27291
  }
27292
27292
  return { variables: result };
27293
27293
  }
27294
- function getVariableValueByPath(path9, variables) {
27295
- const parts = path9.split(".");
27294
+ function getVariableValueByPath(path10, variables) {
27295
+ const parts = path10.split(".");
27296
27296
  let current = variables;
27297
27297
  for (const part of parts) {
27298
27298
  if (current === null || current === void 0) {
@@ -27657,8 +27657,8 @@ function evaluateValueExpression(expr, runtimeVariables) {
27657
27657
  } else {
27658
27658
  return { value: String(varValue), error: `Cannot access path on non-object value` };
27659
27659
  }
27660
- const path9 = pathPart.replace(/^\./, "").replace(/\[(\d+)\]/g, ".$1");
27661
- const parts = path9.split(".").filter((p) => p !== "");
27660
+ const path10 = pathPart.replace(/^\./, "").replace(/\[(\d+)\]/g, ".$1");
27661
+ const parts = path10.split(".").filter((p) => p !== "");
27662
27662
  let current = dataToNavigate;
27663
27663
  for (const part of parts) {
27664
27664
  if (current === null || current === void 0) {
@@ -27737,8 +27737,8 @@ function resolveBareVariables(text, variables) {
27737
27737
  if (varName in variables) {
27738
27738
  const value = variables[varName];
27739
27739
  if (pathPart) {
27740
- const path9 = pathPart.replace(/^\./, "");
27741
- const nestedValue = getNestedValueFromObject(value, path9);
27740
+ const path10 = pathPart.replace(/^\./, "");
27741
+ const nestedValue = getNestedValueFromObject(value, path10);
27742
27742
  return valueToString2(nestedValue);
27743
27743
  }
27744
27744
  return valueToString2(value);
@@ -27758,8 +27758,8 @@ function resolveBareVariables(text, variables) {
27758
27758
  if (varName in variables) {
27759
27759
  const value = variables[varName];
27760
27760
  if (pathPart) {
27761
- const path9 = pathPart.replace(/^\./, "");
27762
- return valueToString2(getNestedValueFromObject(value, path9));
27761
+ const path10 = pathPart.replace(/^\./, "");
27762
+ return valueToString2(getNestedValueFromObject(value, path10));
27763
27763
  }
27764
27764
  return valueToString2(value);
27765
27765
  }
@@ -27796,11 +27796,11 @@ function splitExpressionParts(expr) {
27796
27796
  }
27797
27797
  return parts;
27798
27798
  }
27799
- function getNestedValueFromObject(obj, path9) {
27800
- if (!path9) {
27799
+ function getNestedValueFromObject(obj, path10) {
27800
+ if (!path10) {
27801
27801
  return obj;
27802
27802
  }
27803
- const parts = path9.split(/\.|\[(\d+)\]/).filter((p) => p !== "" && p !== void 0);
27803
+ const parts = path10.split(/\.|\[(\d+)\]/).filter((p) => p !== "" && p !== void 0);
27804
27804
  let current = obj;
27805
27805
  for (const part of parts) {
27806
27806
  if (current === null || current === void 0) {
@@ -28145,24 +28145,24 @@ function extractCaptureDirectives(content) {
28145
28145
  for (const line2 of content.split("\n")) {
28146
28146
  const match = line2.trim().match(captureRegex);
28147
28147
  if (match) {
28148
- let path9 = match[3] || "";
28149
- if (path9.startsWith(".")) {
28150
- path9 = path9.substring(1);
28148
+ let path10 = match[3] || "";
28149
+ if (path10.startsWith(".")) {
28150
+ path10 = path10.substring(1);
28151
28151
  }
28152
28152
  captures.push({
28153
28153
  varName: match[1],
28154
28154
  afterRequest: parseInt(match[2], 10),
28155
- path: path9
28155
+ path: path10
28156
28156
  });
28157
28157
  }
28158
28158
  }
28159
28159
  return captures;
28160
28160
  }
28161
- function getValueByPath(response, path9) {
28162
- if (!path9) {
28161
+ function getValueByPath(response, path10) {
28162
+ if (!path10) {
28163
28163
  return void 0;
28164
28164
  }
28165
- const parts = path9.replace(/\[(\d+)\]/g, ".$1").split(".").filter((p) => p !== "");
28165
+ const parts = path10.replace(/\[(\d+)\]/g, ".$1").split(".").filter((p) => p !== "");
28166
28166
  if (parts.length === 0) {
28167
28167
  return void 0;
28168
28168
  }
@@ -28203,7 +28203,7 @@ function getValueByPath(response, path9) {
28203
28203
  return void 0;
28204
28204
  }
28205
28205
  }
28206
- async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, workingDir, fullDocumentText, onProgress, callStack, sequenceArgs, apiDefinitions, tagFilterOptions, sequenceSources, executionContext) {
28206
+ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, workingDir, fullDocumentText, onProgress, callStack, sequenceArgs, apiDefinitions, tagFilterOptions, sequenceSources, executionContext, debugHooks) {
28207
28207
  const startTime = Date.now();
28208
28208
  const responses = [];
28209
28209
  const scriptResults = [];
@@ -28246,566 +28246,108 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
28246
28246
  let requestIndex = 0;
28247
28247
  const ifStack = [];
28248
28248
  const shouldSkip = () => ifStack.length > 0 && ifStack.some((v) => !v);
28249
- for (let stepIdx = 0; stepIdx < steps.length; stepIdx++) {
28250
- const step = steps[stepIdx];
28251
- if (step.type === "if") {
28252
- if (shouldSkip()) {
28253
- ifStack.push(false);
28254
- } else {
28255
- const conditionMet = evaluateIfCondition(step.content, responses, runtimeVariables, getValueByPath);
28256
- ifStack.push(conditionMet);
28257
- const stepResult = {
28258
- type: "print",
28259
- stepIndex: stepIdx,
28260
- print: {
28261
- title: `if ${step.content}: ${conditionMet ? "true" : "false"}`,
28262
- timestamp: Date.now() - startTime
28263
- }
28264
- };
28265
- orderedSteps.push(stepResult);
28266
- reportProgress(stepIdx, "if", `if ${step.content} \u2192 ${conditionMet}`, stepResult);
28267
- }
28268
- continue;
28269
- }
28270
- if (step.type === "endif") {
28271
- if (ifStack.length > 0) {
28272
- ifStack.pop();
28273
- }
28274
- continue;
28275
- }
28276
- if (shouldSkip()) {
28277
- continue;
28278
- }
28279
- if (step.type === "assertion") {
28280
- const parsed = parseAssertCommand(substituteVariables(step.content, runtimeVariables));
28281
- if (!parsed) {
28282
- errors.push(`Step ${stepIdx + 1}: Invalid assert command: ${step.content}`);
28283
- return {
28284
- name: "",
28285
- success: false,
28286
- responses,
28287
- scriptResults,
28288
- assertionResults,
28289
- steps: orderedSteps,
28290
- errors,
28291
- duration: Date.now() - startTime
28292
- };
28293
- }
28294
- const result = evaluateAssertion(parsed, responses, runtimeVariables, getValueByPath, responseIndexToVariable, workingDir);
28295
- assertionResults.push(result);
28296
- const stepResult = {
28297
- type: "assertion",
28298
- stepIndex: stepIdx,
28299
- assertion: result,
28300
- lineNumber: step.lineNumber
28301
- };
28302
- orderedSteps.push(stepResult);
28303
- const statusIcon = result.passed ? "\u2713" : "\u2717";
28304
- reportProgress(stepIdx, "assertion", `${statusIcon} assert ${result.expression}`, stepResult);
28305
- if (!result.passed) {
28306
- const failMessage = result.message ? `Assertion failed: ${result.message}` : `Assertion failed: ${result.expression}`;
28307
- errors.push(failMessage);
28308
- if (result.error) {
28309
- errors.push(` Error: ${result.error}`);
28310
- } else {
28311
- errors.push(` Expected: ${result.rightExpression ?? result.operator}`);
28312
- errors.push(` Actual: ${JSON.stringify(result.leftValue)}`);
28313
- }
28314
- return {
28315
- name: "",
28316
- success: false,
28317
- responses,
28318
- scriptResults,
28319
- assertionResults,
28320
- steps: orderedSteps,
28321
- errors,
28322
- duration: Date.now() - startTime
28323
- };
28324
- }
28325
- continue;
28326
- }
28327
- if (step.type === "print") {
28328
- const parsed = parsePrintCommand(step.content);
28329
- const titleResolved = resolveBareVariables(parsed.title, runtimeVariables);
28330
- const title = substituteVariables(titleResolved, runtimeVariables);
28331
- const bodyResolved = parsed.body ? resolveBareVariables(parsed.body, runtimeVariables) : void 0;
28332
- const body = bodyResolved ? substituteVariables(bodyResolved, runtimeVariables) : void 0;
28333
- const stepResult = {
28334
- type: "print",
28335
- stepIndex: stepIdx,
28336
- print: {
28337
- title,
28338
- body,
28339
- timestamp: Date.now() - startTime
28340
- }
28341
- };
28342
- orderedSteps.push(stepResult);
28343
- reportProgress(stepIdx, "print", `print: ${title}`, stepResult);
28344
- continue;
28345
- }
28346
- if (step.type === "wait") {
28347
- const durationMs = parseWaitCommand(step.content);
28348
- reportProgress(stepIdx, "wait", `wait: ${durationMs}ms`);
28349
- await sleep2(durationMs);
28350
- const stepResult = {
28351
- type: "print",
28352
- stepIndex: stepIdx,
28353
- print: {
28354
- title: `Waited ${durationMs >= 1e3 ? durationMs / 1e3 + "s" : durationMs + "ms"}`,
28355
- timestamp: Date.now() - startTime
28356
- }
28357
- };
28358
- orderedSteps.push(stepResult);
28359
- continue;
28360
- }
28361
- if (step.type === "json") {
28362
- const bareResolved = resolveBareVariables(step.content, runtimeVariables);
28363
- const parsed = parseJsonCommand(substituteVariables(bareResolved, runtimeVariables));
28364
- if (!parsed) {
28365
- errors.push(`Step ${stepIdx + 1}: Invalid json command: ${step.content}`);
28366
- return {
28367
- name: "",
28368
- success: false,
28369
- responses,
28370
- scriptResults,
28371
- assertionResults,
28372
- steps: orderedSteps,
28373
- errors,
28374
- duration: Date.now() - startTime
28375
- };
28376
- }
28377
- const result = readJsonFile(parsed.filePath, workingDir);
28378
- const stepResult = {
28379
- type: "json",
28380
- stepIndex: stepIdx,
28381
- json: {
28382
- varName: parsed.varName,
28383
- filePath: result.filePath,
28384
- success: result.success,
28385
- error: result.error,
28386
- timestamp: Date.now() - startTime
28387
- }
28388
- };
28389
- orderedSteps.push(stepResult);
28390
- if (!result.success) {
28391
- errors.push(`Failed to load JSON file: ${result.error}`);
28392
- reportProgress(stepIdx, "json", `json: ${parsed.varName} \u2717 ${result.error}`, stepResult);
28393
- return {
28394
- name: "",
28395
- success: false,
28396
- responses,
28397
- scriptResults,
28398
- assertionResults,
28399
- steps: orderedSteps,
28400
- errors,
28401
- duration: Date.now() - startTime
28402
- };
28403
- }
28404
- runtimeVariables[parsed.varName] = JSON.stringify(result.data);
28405
- reportProgress(stepIdx, "json", `json: ${parsed.varName} = ${parsed.filePath}`, stepResult);
28406
- continue;
28249
+ const resolveDebugStepLocation = (step) => {
28250
+ const sequenceName = executionContext?.sequenceName;
28251
+ const indexed = sequenceName ? executionContext?.sequenceLocationIndex?.get(sequenceName.toLowerCase()) : void 0;
28252
+ const startLine = executionContext?.sequenceStartLine ?? indexed?.startLine;
28253
+ return {
28254
+ filePath: executionContext?.filePath ?? indexed?.filePath,
28255
+ sequenceName,
28256
+ declarationLine: indexed?.declarationLine ?? executionContext?.sequenceStartLine,
28257
+ absoluteLine: startLine !== void 0 ? startLine + 1 + step.lineNumber : void 0,
28258
+ relativeLine: step.lineNumber,
28259
+ nestingDepth
28260
+ };
28261
+ };
28262
+ const emitBeforeStep = async (step, stepIdx) => {
28263
+ if (!debugHooks?.beforeStep) {
28264
+ return;
28407
28265
  }
28408
- if (step.type === "propAssign") {
28409
- const parsed = parsePropertyAssignment(step.content);
28410
- if (!parsed) {
28411
- errors.push(`Step ${stepIdx + 1}: Invalid property assignment: ${step.content}`);
28412
- return {
28413
- name: "",
28414
- success: false,
28415
- responses,
28416
- scriptResults,
28417
- assertionResults,
28418
- steps: orderedSteps,
28419
- errors,
28420
- duration: Date.now() - startTime
28421
- };
28422
- }
28423
- if (!(parsed.varName in runtimeVariables)) {
28424
- errors.push(`Step ${stepIdx + 1}: Variable '${parsed.varName}' is not defined`);
28425
- return {
28426
- name: "",
28427
- success: false,
28428
- responses,
28429
- scriptResults,
28430
- assertionResults,
28431
- steps: orderedSteps,
28432
- errors,
28433
- duration: Date.now() - startTime
28434
- };
28435
- }
28436
- let jsonObj;
28437
- try {
28438
- jsonObj = JSON.parse(runtimeVariables[parsed.varName]);
28439
- } catch {
28440
- errors.push(`Step ${stepIdx + 1}: Variable '${parsed.varName}' is not a valid JSON object`);
28441
- return {
28442
- name: "",
28443
- success: false,
28444
- responses,
28445
- scriptResults,
28446
- assertionResults,
28447
- steps: orderedSteps,
28448
- errors,
28449
- duration: Date.now() - startTime
28450
- };
28451
- }
28452
- const valueTrimmed = parsed.value.trim();
28453
- const bareVarMatch = valueTrimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)((?:\.[a-zA-Z_][a-zA-Z0-9_]*|\[\d+\])*)$/);
28454
- let newValue;
28455
- if (bareVarMatch && bareVarMatch[1] in runtimeVariables) {
28456
- const varName = bareVarMatch[1];
28457
- const pathPart = bareVarMatch[2] || "";
28458
- let value = runtimeVariables[varName];
28459
- if (typeof value === "string") {
28460
- try {
28461
- const parsed2 = JSON.parse(value);
28462
- if (typeof parsed2 === "object" && parsed2 !== null) {
28463
- value = parsed2;
28464
- }
28465
- } catch {
28466
- }
28467
- }
28468
- if (pathPart) {
28469
- const path9 = pathPart.replace(/^\./, "");
28470
- newValue = getNestedValueFromObject(value, path9);
28471
- } else {
28472
- newValue = value;
28473
- }
28474
- } else {
28475
- const bareResolvedValue = resolveBareVariables(parsed.value, runtimeVariables);
28476
- const resolvedValue = substituteVariables(bareResolvedValue, runtimeVariables);
28477
- newValue = resolvedValue;
28478
- try {
28479
- newValue = JSON.parse(resolvedValue);
28480
- } catch {
28481
- }
28482
- }
28483
- const success = setNestedValue(jsonObj, parsed.propertyPath, newValue);
28484
- if (!success) {
28485
- errors.push(`Step ${stepIdx + 1}: Failed to set property '${parsed.propertyPath}' on '${parsed.varName}'`);
28486
- return {
28487
- name: "",
28488
- success: false,
28489
- responses,
28490
- scriptResults,
28491
- assertionResults,
28492
- steps: orderedSteps,
28493
- errors,
28494
- duration: Date.now() - startTime
28495
- };
28496
- }
28497
- runtimeVariables[parsed.varName] = JSON.stringify(jsonObj);
28498
- reportProgress(stepIdx, "propAssign", `${parsed.varName}.${parsed.propertyPath} = ${parsed.value}`, void 0);
28499
- continue;
28266
+ await debugHooks.beforeStep({
28267
+ stepType: step.type,
28268
+ stepContent: step.content,
28269
+ stepIndex: stepIdx,
28270
+ totalSteps,
28271
+ location: resolveDebugStepLocation(step),
28272
+ runtimeVariables,
28273
+ responses
28274
+ });
28275
+ };
28276
+ const emitAfterStep = async (step, stepIdx) => {
28277
+ if (!debugHooks?.afterStep) {
28278
+ return;
28500
28279
  }
28501
- if (step.type === "varAssign") {
28502
- const parsed = parseVarAssignCommand(step.content);
28503
- if (!parsed) {
28504
- errors.push(`Step ${stepIdx + 1}: Invalid variable assignment: ${step.content}`);
28505
- return {
28506
- name: "",
28507
- success: false,
28508
- responses,
28509
- scriptResults,
28510
- assertionResults,
28511
- steps: orderedSteps,
28512
- errors,
28513
- duration: Date.now() - startTime
28514
- };
28515
- }
28516
- const result = evaluateValueExpression(parsed.valueExpr, runtimeVariables);
28517
- if (result.error) {
28518
- errors.push(`Step ${stepIdx + 1}: ${result.error}`);
28519
- return {
28520
- name: "",
28521
- success: false,
28522
- responses,
28523
- scriptResults,
28524
- assertionResults,
28525
- steps: orderedSteps,
28526
- errors,
28527
- duration: Date.now() - startTime
28528
- };
28529
- }
28530
- runtimeVariables[parsed.varName] = result.value;
28531
- reportProgress(stepIdx, "propAssign", `var ${parsed.varName} = ${result.value}`, void 0);
28532
- continue;
28280
+ await debugHooks.afterStep({
28281
+ stepType: step.type,
28282
+ stepContent: step.content,
28283
+ stepIndex: stepIdx,
28284
+ totalSteps,
28285
+ location: resolveDebugStepLocation(step),
28286
+ runtimeVariables,
28287
+ responses
28288
+ });
28289
+ };
28290
+ const emitFailure = async (message, step, stepIdx) => {
28291
+ if (!debugHooks?.onFailure) {
28292
+ return;
28533
28293
  }
28534
- if (step.type === "varRequest") {
28535
- const commandLine = step.content.split("\n")[0];
28536
- const parsed = parseVarRequestCommand(commandLine);
28537
- if (!parsed) {
28538
- errors.push(`Step ${stepIdx + 1}: Invalid variable request: ${commandLine}`);
28539
- return {
28540
- name: "",
28541
- success: false,
28542
- responses,
28543
- scriptResults,
28544
- assertionResults,
28545
- steps: orderedSteps,
28546
- errors,
28547
- duration: Date.now() - startTime
28548
- };
28549
- }
28550
- requestIndex++;
28551
- let resolvedUrl = "";
28552
- let resolvedHeaders = {};
28553
- let requestDescription = `${parsed.method} ${parsed.url}`;
28294
+ await debugHooks.onFailure({
28295
+ message,
28296
+ stepIndex: stepIdx,
28297
+ location: step ? resolveDebugStepLocation(step) : void 0,
28298
+ runtimeVariables,
28299
+ responses
28300
+ });
28301
+ };
28302
+ if (debugHooks?.onSequenceEnter) {
28303
+ await debugHooks.onSequenceEnter({
28304
+ sequenceName: executionContext?.sequenceName,
28305
+ filePath: executionContext?.filePath,
28306
+ declarationLine: executionContext?.sequenceStartLine,
28307
+ nestingDepth,
28308
+ runtimeVariables,
28309
+ responses
28310
+ });
28311
+ }
28312
+ try {
28313
+ for (let stepIdx = 0; stepIdx < steps.length; stepIdx++) {
28314
+ const step = steps[stepIdx];
28315
+ await emitBeforeStep(step, stepIdx);
28554
28316
  try {
28555
- const endpointMatch = parsed.url.match(/^([a-zA-Z_][a-zA-Z0-9_]*)(?:\(([^)]*)\))?(\s+.*)?$/);
28556
- const potentialEndpointName = endpointMatch ? endpointMatch[1] : null;
28557
- const endpoint = apiDefinitions && potentialEndpointName ? getEndpoint({ headerGroups: apiDefinitions.headerGroups, endpoints: apiDefinitions.endpoints }, potentialEndpointName) : void 0;
28558
- if (endpoint && apiDefinitions) {
28559
- const paramsStr = endpointMatch[2] || "";
28560
- const headerGroupsStr = endpointMatch[3]?.trim() || "";
28561
- const paramTokens = paramsStr ? paramsStr.split(",").map((p) => p.trim()) : [];
28562
- const params = {};
28563
- const isNamedSyntax = paramTokens.length > 0 && paramTokens[0].includes(":");
28564
- if (isNamedSyntax) {
28565
- for (const token of paramTokens) {
28566
- const kvMatch = token.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.+)$/);
28567
- if (kvMatch) {
28568
- const paramName = kvMatch[1];
28569
- let paramValue = kvMatch[2].trim().replace(/^["']|["']$/g, "");
28570
- const bareResolved = resolveBareVariables(paramValue, runtimeVariables);
28571
- params[paramName] = substituteVariables(bareResolved, runtimeVariables);
28572
- }
28573
- }
28317
+ if (step.type === "if") {
28318
+ if (shouldSkip()) {
28319
+ ifStack.push(false);
28574
28320
  } else {
28575
- endpoint.parameters.forEach((paramName, idx) => {
28576
- if (paramTokens[idx] !== void 0) {
28577
- let paramValue = paramTokens[idx].replace(/^["']|["']$/g, "");
28578
- const bareResolved = resolveBareVariables(paramValue, runtimeVariables);
28579
- params[paramName] = substituteVariables(bareResolved, runtimeVariables);
28580
- }
28581
- });
28582
- }
28583
- let pathWithVars = substituteVariables(endpoint.path, runtimeVariables);
28584
- for (const paramName of endpoint.parameters) {
28585
- const value = params[paramName];
28586
- if (value !== void 0) {
28587
- pathWithVars = pathWithVars.replace(`{${paramName}}`, value);
28588
- }
28589
- }
28590
- resolvedUrl = pathWithVars;
28591
- if (headerGroupsStr) {
28592
- const headerGroupNames = headerGroupsStr.split(/\s+/).filter((n) => n);
28593
- for (const groupName of headerGroupNames) {
28594
- const group = getHeaderGroup(
28595
- { headerGroups: apiDefinitions.headerGroups, endpoints: apiDefinitions.endpoints },
28596
- groupName
28597
- );
28598
- if (group) {
28599
- const groupHeaders = resolveHeaderValues(group, runtimeVariables);
28600
- Object.assign(resolvedHeaders, groupHeaders);
28321
+ const conditionMet = evaluateIfCondition(step.content, responses, runtimeVariables, getValueByPath);
28322
+ ifStack.push(conditionMet);
28323
+ const stepResult = {
28324
+ type: "print",
28325
+ stepIndex: stepIdx,
28326
+ print: {
28327
+ title: `if ${step.content}: ${conditionMet ? "true" : "false"}`,
28328
+ timestamp: Date.now() - startTime
28601
28329
  }
28602
- }
28603
- }
28604
- requestDescription = `${potentialEndpointName}(${paramTokens.join(", ")}) \u2192 ${parsed.method} ${resolvedUrl}`;
28605
- } else {
28606
- resolvedUrl = substituteVariables(parsed.url, runtimeVariables);
28607
- requestDescription = `${parsed.method} ${resolvedUrl}`;
28608
- }
28609
- let requestText = `${parsed.method} ${resolvedUrl}`;
28610
- if (Object.keys(resolvedHeaders).length > 0) {
28611
- const headerLines = Object.entries(resolvedHeaders).map(([k, v]) => `${k}: ${v}`);
28612
- requestText += "\n" + headerLines.join("\n");
28613
- }
28614
- const contentLines = step.content.split("\n");
28615
- if (contentLines.length > 1) {
28616
- const extraLines = contentLines.slice(1).map((line2) => substituteVariables(line2, runtimeVariables));
28617
- const hasExplicitBlank = extraLines.some((line2) => line2.trim() === "");
28618
- const firstNonEmpty = extraLines.find((line2) => line2.trim() !== "");
28619
- const startsWithHeader = firstNonEmpty ? /^[A-Za-z0-9\-_]+\s*:/.test(firstNonEmpty.trim()) : false;
28620
- if (hasExplicitBlank || startsWithHeader) {
28621
- requestText += "\n" + extraLines.join("\n");
28622
- } else {
28623
- requestText += "\n\n" + extraLines.join("\n");
28624
- }
28625
- }
28626
- const requestParsed = parserHttpRequest(requestText, runtimeVariables);
28627
- validatePreparedRequest(
28628
- requestParsed,
28629
- buildRequestValidationContext(stepIdx + 1, requestParsed.method, requestParsed.url, `var ${parsed.varName}`)
28630
- );
28631
- const retryOpts = parsed.retryCount ? {
28632
- retryCount: parsed.retryCount,
28633
- backoffMs: parsed.backoffMs || 1e3,
28634
- onRetry: (attempt, total, error, waitMs) => {
28635
- reportProgress(stepIdx, "request", `[Retry ${attempt}/${total}] ${requestDescription} - waiting ${waitMs}ms`, void 0);
28636
- }
28637
- } : void 0;
28638
- const response = cookieJar ? await sendRequestWithJar(requestParsed, cookieJar, retryOpts) : await sendRequest(requestParsed, retryOpts);
28639
- addResponse(response);
28640
- responseIndexToVariable.set(responses.length, parsed.varName);
28641
- runtimeVariables[parsed.varName] = response;
28642
- const stepResult = {
28643
- type: "request",
28644
- stepIndex: stepIdx,
28645
- response,
28646
- requestMethod: parsed.method,
28647
- requestUrl: resolvedUrl,
28648
- variableName: parsed.varName
28649
- };
28650
- orderedSteps.push(stepResult);
28651
- reportProgress(stepIdx, "request", `var ${parsed.varName} = ${requestDescription}`, stepResult);
28652
- const relevantCaptures = captures.filter((c) => c.afterRequest === requestIndex);
28653
- for (const capture of relevantCaptures) {
28654
- const value = getValueByPath(response, capture.path);
28655
- if (value !== void 0) {
28656
- runtimeVariables[capture.varName] = value;
28657
- } else {
28658
- errors.push(`Warning: Could not capture ${capture.varName} from $${requestIndex}.${capture.path}`);
28330
+ };
28331
+ orderedSteps.push(stepResult);
28332
+ reportProgress(stepIdx, "if", `if ${step.content} \u2192 ${conditionMet}`, stepResult);
28659
28333
  }
28334
+ continue;
28660
28335
  }
28661
- } catch (error) {
28662
- const userMessage = formatUserFacingError(
28663
- error,
28664
- buildRequestValidationContext(stepIdx + 1, void 0, void 0, `var ${parsed.varName}`)
28665
- );
28666
- let errorDetails = `Request failed for var ${parsed.varName}:
28667
- ${indentMultiline(userMessage)}`;
28668
- if (requestDescription && !/\nRequest:\s/.test(userMessage)) {
28669
- errorDetails += `
28670
- Request: ${requestDescription}`;
28671
- }
28672
- errors.push(errorDetails);
28673
- return {
28674
- name: "",
28675
- success: false,
28676
- responses,
28677
- scriptResults,
28678
- assertionResults,
28679
- steps: orderedSteps,
28680
- errors,
28681
- duration: Date.now() - startTime
28682
- };
28683
- }
28684
- continue;
28685
- }
28686
- if (step.type === "script") {
28687
- const parsed = parseRunCommand(substituteVariables(step.content, runtimeVariables));
28688
- if (!parsed) {
28689
- errors.push(`Step ${stepIdx + 1}: Invalid run command: ${step.content}`);
28690
- return {
28691
- name: "",
28692
- success: false,
28693
- responses,
28694
- scriptResults,
28695
- assertionResults,
28696
- steps: orderedSteps,
28697
- errors,
28698
- duration: Date.now() - startTime
28699
- };
28700
- }
28701
- const substitutedArgs = parsed.args.map((arg) => {
28702
- const bareResolved = resolveBareVariables(arg, runtimeVariables);
28703
- return substituteVariables(bareResolved, runtimeVariables);
28704
- });
28705
- const substitutedPath = substituteVariables(parsed.scriptPath, runtimeVariables);
28706
- try {
28707
- const result = await runScript(
28708
- parsed.type,
28709
- substitutedPath,
28710
- substitutedArgs,
28711
- workingDir || process.cwd(),
28712
- runtimeVariables,
28713
- parsed.captureAs
28714
- );
28715
- scriptResults.push(result);
28716
- const stepResult = {
28717
- type: "script",
28718
- stepIndex: stepIdx,
28719
- script: result
28720
- };
28721
- orderedSteps.push(stepResult);
28722
- reportProgress(stepIdx, "script", `run ${parsed.type} ${substitutedPath}`, stepResult);
28723
- if (!result.success) {
28724
- errors.push(`Script failed (exit ${result.exitCode}): ${result.error || result.output}`);
28725
- return {
28726
- name: "",
28727
- success: false,
28728
- responses,
28729
- scriptResults,
28730
- assertionResults,
28731
- steps: orderedSteps,
28732
- errors,
28733
- duration: Date.now() - startTime
28734
- };
28735
- }
28736
- if (parsed.captureAs) {
28737
- let outputValue = result.output;
28738
- try {
28739
- const jsonParsed = JSON.parse(result.output);
28740
- outputValue = jsonParsed;
28741
- } catch {
28336
+ if (step.type === "endif") {
28337
+ if (ifStack.length > 0) {
28338
+ ifStack.pop();
28742
28339
  }
28743
- runtimeVariables[parsed.captureAs] = outputValue;
28340
+ continue;
28744
28341
  }
28745
- } catch (error) {
28746
- errors.push(`Script execution error: ${error.message}`);
28747
- return {
28748
- name: "",
28749
- success: false,
28750
- responses,
28751
- scriptResults,
28752
- assertionResults,
28753
- steps: orderedSteps,
28754
- errors,
28755
- duration: Date.now() - startTime
28756
- };
28757
- }
28758
- } else if (step.type === "namedRequest") {
28759
- const parsed = parseRunNamedRequestCommand(step.content);
28760
- if (!parsed) {
28761
- errors.push(`Invalid run command: ${step.content}`);
28762
- return {
28763
- name: "",
28764
- success: false,
28765
- responses,
28766
- scriptResults,
28767
- assertionResults,
28768
- steps: orderedSteps,
28769
- errors,
28770
- duration: Date.now() - startTime
28771
- };
28772
- }
28773
- const { name: targetName, args: runArgs } = parsed;
28774
- if (!fullDocumentText) {
28775
- errors.push(`Cannot run "${targetName}": full document text not provided`);
28776
- return {
28777
- name: "",
28778
- success: false,
28779
- responses,
28780
- scriptResults,
28781
- assertionResults,
28782
- steps: orderedSteps,
28783
- errors,
28784
- duration: Date.now() - startTime
28785
- };
28786
- }
28787
- const allSequences = extractSequences(fullDocumentText);
28788
- const targetSequence = allSequences.find((s) => s.name === targetName);
28789
- if (targetSequence) {
28790
- const currentCallStack = callStack || [];
28791
- if (currentCallStack.includes(targetName)) {
28792
- errors.push(`Circular reference detected: ${[...currentCallStack, targetName].join(" \u2192 ")}`);
28793
- return {
28794
- name: "",
28795
- success: false,
28796
- responses,
28797
- scriptResults,
28798
- assertionResults,
28799
- steps: orderedSteps,
28800
- errors,
28801
- duration: Date.now() - startTime
28802
- };
28342
+ if (shouldSkip()) {
28343
+ continue;
28803
28344
  }
28804
- let sequenceArgs2 = {};
28805
- if (targetSequence.parameters.length > 0 || runArgs.length > 0) {
28806
- const bindResult = bindSequenceArguments(targetSequence.parameters, runArgs, runtimeVariables);
28807
- if ("error" in bindResult) {
28808
- errors.push(`Error calling sequence "${targetName}": ${bindResult.error}`);
28345
+ if (step.type === "assertion") {
28346
+ const parsed = parseAssertCommand(substituteVariables(step.content, runtimeVariables));
28347
+ if (!parsed) {
28348
+ const invalidAssertMessage = `Step ${stepIdx + 1}: Invalid assert command: ${step.content}`;
28349
+ errors.push(invalidAssertMessage);
28350
+ await emitFailure(invalidAssertMessage, step, stepIdx);
28809
28351
  return {
28810
28352
  name: "",
28811
28353
  success: false,
@@ -28817,600 +28359,1164 @@ ${indentMultiline(userMessage)}`;
28817
28359
  duration: Date.now() - startTime
28818
28360
  };
28819
28361
  }
28820
- sequenceArgs2 = bindResult.variables;
28821
- }
28822
- if (tagFilterOptions && tagFilterOptions.filters.length > 0) {
28823
- if (!sequenceMatchesTags(targetSequence, tagFilterOptions)) {
28824
- continue;
28825
- }
28826
- }
28827
- reportProgress(stepIdx, "sequenceStart", `Starting sequence ${targetName}`, void 0, targetName);
28828
- let subWorkingDir = workingDir;
28829
- if (sequenceSources) {
28830
- const sourceFile = sequenceSources.get(targetName.toLowerCase());
28831
- if (sourceFile) {
28832
- const path9 = await import("path");
28833
- subWorkingDir = path9.dirname(sourceFile);
28834
- }
28835
- }
28836
- const subResult = await runSequenceWithJar(
28837
- targetSequence.content,
28838
- runtimeVariables,
28839
- // Pass current runtime variables (includes file vars + any captured)
28840
- cookieJar,
28841
- subWorkingDir,
28842
- fullDocumentText,
28843
- onProgress,
28844
- // Pass through progress callback
28845
- [...currentCallStack, targetName],
28846
- // Add to call stack for circular detection
28847
- sequenceArgs2,
28848
- // Pass bound arguments as initial scope variables
28849
- apiDefinitions,
28850
- // Pass API definitions for endpoint resolution
28851
- tagFilterOptions,
28852
- // Pass tag filter options for nested sequence filtering
28853
- sequenceSources,
28854
- // Pass sequence sources for nested sequences
28855
- executionContext
28856
- );
28857
- reportProgress(stepIdx, "sequenceEnd", `Finished sequence ${targetName}`, void 0, targetName);
28858
- for (const subResponse of subResult.responses) {
28859
- addResponse(subResponse);
28860
- }
28861
- scriptResults.push(...subResult.scriptResults);
28862
- assertionResults.push(...subResult.assertionResults);
28863
- for (const subStep of subResult.steps) {
28864
- orderedSteps.push({
28865
- ...subStep,
28866
- stepIndex: orderedSteps.length
28867
- });
28868
- }
28869
- requestIndex += subResult.responses.length;
28870
- if (!subResult.success) {
28871
- errors.push(`Sequence "${targetName}" failed`);
28872
- errors.push(...subResult.errors);
28873
- return {
28874
- name: "",
28875
- success: false,
28876
- responses,
28877
- scriptResults,
28878
- assertionResults,
28879
- steps: orderedSteps,
28880
- errors,
28881
- duration: Date.now() - startTime,
28882
- variables: runtimeVariables
28362
+ const result = evaluateAssertion(parsed, responses, runtimeVariables, getValueByPath, responseIndexToVariable, workingDir);
28363
+ assertionResults.push(result);
28364
+ const stepResult = {
28365
+ type: "assertion",
28366
+ stepIndex: stepIdx,
28367
+ assertion: result,
28368
+ lineNumber: step.lineNumber
28883
28369
  };
28884
- }
28885
- continue;
28886
- }
28887
- const namedRequest = getNamedRequest(fullDocumentText, targetName);
28888
- if (!namedRequest) {
28889
- errors.push(`"${targetName}" is not a named request or sequence`);
28890
- return {
28891
- name: "",
28892
- success: false,
28893
- responses,
28894
- scriptResults,
28895
- assertionResults,
28896
- steps: orderedSteps,
28897
- errors,
28898
- duration: Date.now() - startTime
28899
- };
28900
- }
28901
- requestIndex++;
28902
- let requestUrl = "";
28903
- let requestMethod = "";
28904
- try {
28905
- let requestParsed;
28906
- if (apiDefinitions && apiDefinitions.endpoints.length > 0 && isApiRequestLine(namedRequest.content, apiDefinitions.endpoints)) {
28907
- const apiRequest = parseApiRequest(
28908
- namedRequest.content,
28909
- apiDefinitions.endpoints,
28910
- apiDefinitions.headerGroups
28911
- );
28912
- if (apiRequest) {
28913
- const endpoint = getEndpoint(
28914
- { headerGroups: apiDefinitions.headerGroups, endpoints: apiDefinitions.endpoints },
28915
- apiRequest.endpointName
28916
- );
28917
- if (endpoint) {
28918
- let resolvedPath = endpoint.path;
28919
- resolvedPath = resolvedPath.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (_, varName) => {
28920
- return runtimeVariables[varName] !== void 0 ? String(runtimeVariables[varName]) : `{{${varName}}}`;
28921
- });
28922
- for (const [paramName, paramValue] of Object.entries(apiRequest.params)) {
28923
- let resolvedValue = paramValue;
28924
- if (runtimeVariables[paramValue] !== void 0) {
28925
- resolvedValue = String(runtimeVariables[paramValue]);
28926
- } else if (paramValue.startsWith("{{") && paramValue.endsWith("}}")) {
28927
- const varName = paramValue.slice(2, -2);
28928
- if (runtimeVariables[varName] !== void 0) {
28929
- resolvedValue = String(runtimeVariables[varName]);
28930
- }
28931
- }
28932
- resolvedPath = resolvedPath.replace(`{${paramName}}`, resolvedValue);
28933
- }
28934
- const combinedHeaders = {};
28935
- for (const groupName of apiRequest.headerGroupNames) {
28936
- const group = getHeaderGroup(apiDefinitions, groupName);
28937
- if (group) {
28938
- const resolvedHeaders = resolveHeaderValues(group, runtimeVariables);
28939
- Object.assign(combinedHeaders, resolvedHeaders);
28940
- }
28941
- }
28942
- for (const [headerName, headerValue] of Object.entries(apiRequest.inlineHeaders)) {
28943
- let resolved = headerValue;
28944
- resolved = resolved.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (_, varName) => {
28945
- return runtimeVariables[varName] !== void 0 ? String(runtimeVariables[varName]) : `{{${varName}}}`;
28946
- });
28947
- combinedHeaders[headerName] = resolved;
28948
- }
28949
- const bodyParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
28950
- requestParsed = {
28951
- method: apiRequest.method,
28952
- url: resolvedPath,
28953
- headers: combinedHeaders,
28954
- body: bodyParsed.body
28955
- };
28370
+ orderedSteps.push(stepResult);
28371
+ const statusIcon = result.passed ? "\u2713" : "\u2717";
28372
+ reportProgress(stepIdx, "assertion", `${statusIcon} assert ${result.expression}`, stepResult);
28373
+ if (!result.passed) {
28374
+ const failMessage = result.message ? `Assertion failed: ${result.message}` : `Assertion failed: ${result.expression}`;
28375
+ errors.push(failMessage);
28376
+ if (result.error) {
28377
+ errors.push(` Error: ${result.error}`);
28956
28378
  } else {
28957
- throw new Error(`Unknown endpoint: ${apiRequest.endpointName}`);
28379
+ errors.push(` Expected: ${result.rightExpression ?? result.operator}`);
28380
+ errors.push(` Actual: ${JSON.stringify(result.leftValue)}`);
28958
28381
  }
28959
- } else {
28960
- requestParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
28961
- }
28962
- } else {
28963
- requestParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
28964
- if (apiDefinitions && apiDefinitions.headerGroups.length > 0) {
28965
- requestParsed = applyHeaderGroupsToRequest(requestParsed, namedRequest.content, apiDefinitions.headerGroups, runtimeVariables);
28966
- }
28967
- }
28968
- requestUrl = requestParsed.url;
28969
- requestMethod = requestParsed.method;
28970
- validatePreparedRequest(
28971
- requestParsed,
28972
- buildRequestValidationContext(stepIdx + 1, requestMethod, requestUrl, targetName)
28973
- );
28974
- const effectiveRetryCount = parsed.retryCount ?? requestParsed.retryCount;
28975
- const effectiveBackoffMs = parsed.backoffMs ?? requestParsed.backoffMs ?? 1e3;
28976
- const retryOpts = effectiveRetryCount ? {
28977
- retryCount: effectiveRetryCount,
28978
- backoffMs: effectiveBackoffMs,
28979
- onRetry: (attempt, total, error, waitMs) => {
28980
- reportProgress(stepIdx, "request", `[Retry ${attempt}/${total}] run ${targetName} - waiting ${waitMs}ms`, void 0);
28981
- }
28982
- } : void 0;
28983
- const response = cookieJar ? await sendRequestWithJar(requestParsed, cookieJar, retryOpts) : await sendRequest(requestParsed, retryOpts);
28984
- addResponse(response);
28985
- const stepResult = {
28986
- type: "request",
28987
- stepIndex: stepIdx,
28988
- response,
28989
- requestMethod: requestParsed.method,
28990
- requestUrl: requestParsed.url
28991
- };
28992
- orderedSteps.push(stepResult);
28993
- reportProgress(stepIdx, "namedRequest", `run ${targetName} \u2192 ${requestParsed.method} ${requestParsed.url}`, stepResult);
28994
- const relevantCaptures = captures.filter((c) => c.afterRequest === requestIndex);
28995
- for (const capture of relevantCaptures) {
28996
- const value = getValueByPath(response, capture.path);
28997
- if (value !== void 0) {
28998
- runtimeVariables[capture.varName] = value;
28999
- } else {
29000
- errors.push(`Warning: Could not capture ${capture.varName} from $${requestIndex}.${capture.path}`);
28382
+ await emitFailure(failMessage, step, stepIdx);
28383
+ return {
28384
+ name: "",
28385
+ success: false,
28386
+ responses,
28387
+ scriptResults,
28388
+ assertionResults,
28389
+ steps: orderedSteps,
28390
+ errors,
28391
+ duration: Date.now() - startTime
28392
+ };
29001
28393
  }
28394
+ continue;
29002
28395
  }
29003
- } catch (error) {
29004
- const userMessage = formatUserFacingError(
29005
- error,
29006
- buildRequestValidationContext(stepIdx + 1, requestMethod || void 0, requestUrl || void 0, targetName)
29007
- );
29008
- let errorDetails = `Named request "${targetName}" failed:
29009
- ${indentMultiline(userMessage)}`;
29010
- if (requestUrl && !/\nRequest:\s/.test(userMessage)) {
29011
- errorDetails += `
29012
- Request: ${requestMethod} ${requestUrl}`;
29013
- }
29014
- errors.push(errorDetails);
29015
- return {
29016
- name: "",
29017
- success: false,
29018
- responses,
29019
- scriptResults,
29020
- assertionResults,
29021
- steps: orderedSteps,
29022
- errors,
29023
- duration: Date.now() - startTime
29024
- };
29025
- }
29026
- } else if (step.type === "varRunSequence") {
29027
- const parsed = parseVarRunSequenceCommand(step.content);
29028
- if (!parsed) {
29029
- errors.push(`Invalid var run sequence command: ${step.content}`);
29030
- return {
29031
- name: "",
29032
- success: false,
29033
- responses,
29034
- scriptResults,
29035
- assertionResults,
29036
- steps: orderedSteps,
29037
- errors,
29038
- duration: Date.now() - startTime
29039
- };
29040
- }
29041
- const { varName, sequenceName, args: runArgs } = parsed;
29042
- if (!fullDocumentText) {
29043
- errors.push(`Cannot run sequence "${sequenceName}": full document text not provided`);
29044
- return {
29045
- name: "",
29046
- success: false,
29047
- responses,
29048
- scriptResults,
29049
- assertionResults,
29050
- steps: orderedSteps,
29051
- errors,
29052
- duration: Date.now() - startTime
29053
- };
29054
- }
29055
- const allSequences = extractSequences(fullDocumentText);
29056
- const targetSequence = allSequences.find((s) => s.name === sequenceName);
29057
- if (!targetSequence) {
29058
- const namedRequest = getNamedRequest(fullDocumentText, sequenceName);
29059
- if (!namedRequest) {
29060
- errors.push(`"${sequenceName}" is not a named request or sequence`);
29061
- return {
29062
- name: "",
29063
- success: false,
29064
- responses,
29065
- scriptResults,
29066
- assertionResults,
29067
- steps: orderedSteps,
29068
- errors,
29069
- duration: Date.now() - startTime
28396
+ if (step.type === "print") {
28397
+ const parsed = parsePrintCommand(step.content);
28398
+ const titleResolved = resolveBareVariables(parsed.title, runtimeVariables);
28399
+ const title = substituteVariables(titleResolved, runtimeVariables);
28400
+ const bodyResolved = parsed.body ? resolveBareVariables(parsed.body, runtimeVariables) : void 0;
28401
+ const body = bodyResolved ? substituteVariables(bodyResolved, runtimeVariables) : void 0;
28402
+ const stepResult = {
28403
+ type: "print",
28404
+ stepIndex: stepIdx,
28405
+ print: {
28406
+ title,
28407
+ body,
28408
+ timestamp: Date.now() - startTime
28409
+ }
29070
28410
  };
28411
+ orderedSteps.push(stepResult);
28412
+ reportProgress(stepIdx, "print", `print: ${title}`, stepResult);
28413
+ continue;
29071
28414
  }
29072
- requestIndex++;
29073
- let requestUrl = "";
29074
- let requestMethod = "";
29075
- try {
29076
- const requestParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
29077
- requestUrl = requestParsed.url;
29078
- requestMethod = requestParsed.method;
29079
- validatePreparedRequest(
29080
- requestParsed,
29081
- buildRequestValidationContext(stepIdx + 1, requestMethod, requestUrl, `${varName} = run ${sequenceName}`)
29082
- );
29083
- const effectiveRetryCount = parsed.retryCount ?? requestParsed.retryCount;
29084
- const effectiveBackoffMs = parsed.backoffMs ?? requestParsed.backoffMs ?? 1e3;
29085
- const retryOpts = effectiveRetryCount ? {
29086
- retryCount: effectiveRetryCount,
29087
- backoffMs: effectiveBackoffMs,
29088
- onRetry: (attempt, total, error, waitMs) => {
29089
- reportProgress(stepIdx, "request", `[Retry ${attempt}/${total}] var ${varName} = run ${sequenceName} - waiting ${waitMs}ms`, void 0);
29090
- }
29091
- } : void 0;
29092
- const response = cookieJar ? await sendRequestWithJar(requestParsed, cookieJar, retryOpts) : await sendRequest(requestParsed, retryOpts);
29093
- addResponse(response);
29094
- responseIndexToVariable.set(responses.length, varName);
28415
+ if (step.type === "wait") {
28416
+ const durationMs = parseWaitCommand(step.content);
28417
+ reportProgress(stepIdx, "wait", `wait: ${durationMs}ms`);
28418
+ await sleep2(durationMs);
29095
28419
  const stepResult = {
29096
- type: "request",
28420
+ type: "print",
29097
28421
  stepIndex: stepIdx,
29098
- response,
29099
- requestMethod: requestParsed.method,
29100
- requestUrl: requestParsed.url,
29101
- variableName: varName
28422
+ print: {
28423
+ title: `Waited ${durationMs >= 1e3 ? durationMs / 1e3 + "s" : durationMs + "ms"}`,
28424
+ timestamp: Date.now() - startTime
28425
+ }
29102
28426
  };
29103
28427
  orderedSteps.push(stepResult);
29104
- reportProgress(stepIdx, "namedRequest", `var ${varName} = run ${sequenceName} \u2192 ${requestParsed.method} ${requestParsed.url}`, stepResult);
29105
- const capturedResponse = {
29106
- status: response.status,
29107
- statusText: response.statusText,
29108
- body: response.body,
29109
- headers: response.headers
28428
+ continue;
28429
+ }
28430
+ if (step.type === "json") {
28431
+ const bareResolved = resolveBareVariables(step.content, runtimeVariables);
28432
+ const parsed = parseJsonCommand(substituteVariables(bareResolved, runtimeVariables));
28433
+ if (!parsed) {
28434
+ errors.push(`Step ${stepIdx + 1}: Invalid json command: ${step.content}`);
28435
+ return {
28436
+ name: "",
28437
+ success: false,
28438
+ responses,
28439
+ scriptResults,
28440
+ assertionResults,
28441
+ steps: orderedSteps,
28442
+ errors,
28443
+ duration: Date.now() - startTime
28444
+ };
28445
+ }
28446
+ const result = readJsonFile(parsed.filePath, workingDir);
28447
+ const stepResult = {
28448
+ type: "json",
28449
+ stepIndex: stepIdx,
28450
+ json: {
28451
+ varName: parsed.varName,
28452
+ filePath: result.filePath,
28453
+ success: result.success,
28454
+ error: result.error,
28455
+ timestamp: Date.now() - startTime
28456
+ }
29110
28457
  };
29111
- runtimeVariables[varName] = JSON.stringify(capturedResponse);
29112
- const relevantCaptures = captures.filter((c) => c.afterRequest === requestIndex);
29113
- for (const capture of relevantCaptures) {
29114
- const value = getValueByPath(response, capture.path);
29115
- if (value !== void 0) {
29116
- runtimeVariables[capture.varName] = value;
28458
+ orderedSteps.push(stepResult);
28459
+ if (!result.success) {
28460
+ errors.push(`Failed to load JSON file: ${result.error}`);
28461
+ reportProgress(stepIdx, "json", `json: ${parsed.varName} \u2717 ${result.error}`, stepResult);
28462
+ return {
28463
+ name: "",
28464
+ success: false,
28465
+ responses,
28466
+ scriptResults,
28467
+ assertionResults,
28468
+ steps: orderedSteps,
28469
+ errors,
28470
+ duration: Date.now() - startTime
28471
+ };
28472
+ }
28473
+ runtimeVariables[parsed.varName] = JSON.stringify(result.data);
28474
+ reportProgress(stepIdx, "json", `json: ${parsed.varName} = ${parsed.filePath}`, stepResult);
28475
+ continue;
28476
+ }
28477
+ if (step.type === "propAssign") {
28478
+ const parsed = parsePropertyAssignment(step.content);
28479
+ if (!parsed) {
28480
+ errors.push(`Step ${stepIdx + 1}: Invalid property assignment: ${step.content}`);
28481
+ return {
28482
+ name: "",
28483
+ success: false,
28484
+ responses,
28485
+ scriptResults,
28486
+ assertionResults,
28487
+ steps: orderedSteps,
28488
+ errors,
28489
+ duration: Date.now() - startTime
28490
+ };
28491
+ }
28492
+ if (!(parsed.varName in runtimeVariables)) {
28493
+ errors.push(`Step ${stepIdx + 1}: Variable '${parsed.varName}' is not defined`);
28494
+ return {
28495
+ name: "",
28496
+ success: false,
28497
+ responses,
28498
+ scriptResults,
28499
+ assertionResults,
28500
+ steps: orderedSteps,
28501
+ errors,
28502
+ duration: Date.now() - startTime
28503
+ };
28504
+ }
28505
+ let jsonObj;
28506
+ try {
28507
+ jsonObj = JSON.parse(runtimeVariables[parsed.varName]);
28508
+ } catch {
28509
+ errors.push(`Step ${stepIdx + 1}: Variable '${parsed.varName}' is not a valid JSON object`);
28510
+ return {
28511
+ name: "",
28512
+ success: false,
28513
+ responses,
28514
+ scriptResults,
28515
+ assertionResults,
28516
+ steps: orderedSteps,
28517
+ errors,
28518
+ duration: Date.now() - startTime
28519
+ };
28520
+ }
28521
+ const valueTrimmed = parsed.value.trim();
28522
+ const bareVarMatch = valueTrimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)((?:\.[a-zA-Z_][a-zA-Z0-9_]*|\[\d+\])*)$/);
28523
+ let newValue;
28524
+ if (bareVarMatch && bareVarMatch[1] in runtimeVariables) {
28525
+ const varName = bareVarMatch[1];
28526
+ const pathPart = bareVarMatch[2] || "";
28527
+ let value = runtimeVariables[varName];
28528
+ if (typeof value === "string") {
28529
+ try {
28530
+ const parsed2 = JSON.parse(value);
28531
+ if (typeof parsed2 === "object" && parsed2 !== null) {
28532
+ value = parsed2;
28533
+ }
28534
+ } catch {
28535
+ }
28536
+ }
28537
+ if (pathPart) {
28538
+ const path10 = pathPart.replace(/^\./, "");
28539
+ newValue = getNestedValueFromObject(value, path10);
29117
28540
  } else {
29118
- errors.push(`Warning: Could not capture ${capture.varName} from $${requestIndex}.${capture.path}`);
28541
+ newValue = value;
28542
+ }
28543
+ } else {
28544
+ const bareResolvedValue = resolveBareVariables(parsed.value, runtimeVariables);
28545
+ const resolvedValue = substituteVariables(bareResolvedValue, runtimeVariables);
28546
+ newValue = resolvedValue;
28547
+ try {
28548
+ newValue = JSON.parse(resolvedValue);
28549
+ } catch {
29119
28550
  }
29120
28551
  }
29121
- } catch (error) {
29122
- const userMessage = formatUserFacingError(
29123
- error,
29124
- buildRequestValidationContext(stepIdx + 1, requestMethod || void 0, requestUrl || void 0, `${varName} = run ${sequenceName}`)
29125
- );
29126
- let errorDetails = `Named request "${sequenceName}" failed:
29127
- ${indentMultiline(userMessage)}`;
29128
- if (requestUrl && !/\nRequest:\s/.test(userMessage)) {
29129
- errorDetails += `
29130
- Request: ${requestMethod} ${requestUrl}`;
28552
+ const success = setNestedValue(jsonObj, parsed.propertyPath, newValue);
28553
+ if (!success) {
28554
+ errors.push(`Step ${stepIdx + 1}: Failed to set property '${parsed.propertyPath}' on '${parsed.varName}'`);
28555
+ return {
28556
+ name: "",
28557
+ success: false,
28558
+ responses,
28559
+ scriptResults,
28560
+ assertionResults,
28561
+ steps: orderedSteps,
28562
+ errors,
28563
+ duration: Date.now() - startTime
28564
+ };
29131
28565
  }
29132
- errors.push(errorDetails);
29133
- return {
29134
- name: "",
29135
- success: false,
29136
- responses,
29137
- scriptResults,
29138
- assertionResults,
29139
- steps: orderedSteps,
29140
- errors,
29141
- duration: Date.now() - startTime
29142
- };
29143
- }
29144
- continue;
29145
- }
29146
- const currentCallStack = callStack || [];
29147
- if (currentCallStack.includes(sequenceName)) {
29148
- errors.push(`Circular reference detected: ${[...currentCallStack, sequenceName].join(" \u2192 ")}`);
29149
- return {
29150
- name: "",
29151
- success: false,
29152
- responses,
29153
- scriptResults,
29154
- assertionResults,
29155
- steps: orderedSteps,
29156
- errors,
29157
- duration: Date.now() - startTime
29158
- };
29159
- }
29160
- let sequenceArgs2 = {};
29161
- if (targetSequence.parameters.length > 0 || runArgs.length > 0) {
29162
- const bindResult = bindSequenceArguments(targetSequence.parameters, runArgs, runtimeVariables);
29163
- if ("error" in bindResult) {
29164
- errors.push(`Error calling sequence "${sequenceName}": ${bindResult.error}`);
29165
- return {
29166
- name: "",
29167
- success: false,
29168
- responses,
29169
- scriptResults,
29170
- assertionResults,
29171
- steps: orderedSteps,
29172
- errors,
29173
- duration: Date.now() - startTime
29174
- };
29175
- }
29176
- sequenceArgs2 = bindResult.variables;
29177
- }
29178
- if (tagFilterOptions && tagFilterOptions.filters.length > 0) {
29179
- if (!sequenceMatchesTags(targetSequence, tagFilterOptions)) {
28566
+ runtimeVariables[parsed.varName] = JSON.stringify(jsonObj);
28567
+ reportProgress(stepIdx, "propAssign", `${parsed.varName}.${parsed.propertyPath} = ${parsed.value}`, void 0);
29180
28568
  continue;
29181
28569
  }
29182
- }
29183
- reportProgress(stepIdx, "sequenceStart", `Starting sequence ${sequenceName}`, void 0, sequenceName);
29184
- let subWorkingDir = workingDir;
29185
- if (sequenceSources) {
29186
- const sourceFile = sequenceSources.get(sequenceName.toLowerCase());
29187
- if (sourceFile) {
29188
- const path9 = await import("path");
29189
- subWorkingDir = path9.dirname(sourceFile);
28570
+ if (step.type === "varAssign") {
28571
+ const parsed = parseVarAssignCommand(step.content);
28572
+ if (!parsed) {
28573
+ errors.push(`Step ${stepIdx + 1}: Invalid variable assignment: ${step.content}`);
28574
+ return {
28575
+ name: "",
28576
+ success: false,
28577
+ responses,
28578
+ scriptResults,
28579
+ assertionResults,
28580
+ steps: orderedSteps,
28581
+ errors,
28582
+ duration: Date.now() - startTime
28583
+ };
28584
+ }
28585
+ const result = evaluateValueExpression(parsed.valueExpr, runtimeVariables);
28586
+ if (result.error) {
28587
+ errors.push(`Step ${stepIdx + 1}: ${result.error}`);
28588
+ return {
28589
+ name: "",
28590
+ success: false,
28591
+ responses,
28592
+ scriptResults,
28593
+ assertionResults,
28594
+ steps: orderedSteps,
28595
+ errors,
28596
+ duration: Date.now() - startTime
28597
+ };
28598
+ }
28599
+ runtimeVariables[parsed.varName] = result.value;
28600
+ reportProgress(stepIdx, "propAssign", `var ${parsed.varName} = ${result.value}`, void 0);
28601
+ continue;
29190
28602
  }
29191
- }
29192
- const subResult = await runSequenceWithJar(
29193
- targetSequence.content,
29194
- runtimeVariables,
29195
- cookieJar,
29196
- subWorkingDir,
29197
- fullDocumentText,
29198
- onProgress,
29199
- [...currentCallStack, sequenceName],
29200
- sequenceArgs2,
29201
- // Pass bound arguments
29202
- apiDefinitions,
29203
- // Pass API definitions for endpoint resolution
29204
- tagFilterOptions,
29205
- // Pass tag filter options for nested sequence filtering
29206
- sequenceSources,
29207
- // Pass sequence sources for nested sequences
29208
- executionContext
29209
- );
29210
- reportProgress(stepIdx, "sequenceEnd", `Finished sequence ${sequenceName}`, void 0, sequenceName);
29211
- for (const subResponse of subResult.responses) {
29212
- addResponse(subResponse);
29213
- }
29214
- scriptResults.push(...subResult.scriptResults);
29215
- assertionResults.push(...subResult.assertionResults);
29216
- for (const subStep of subResult.steps) {
29217
- orderedSteps.push({
29218
- ...subStep,
29219
- stepIndex: orderedSteps.length
29220
- });
29221
- }
29222
- const returnExpressions = extractReturnVariables(targetSequence.content);
29223
- if (returnExpressions && returnExpressions.length > 0 && subResult.variables) {
29224
- if (returnExpressions.length === 1) {
29225
- const resolved = resolveReturnExpression(returnExpressions[0], subResult.variables);
29226
- if (resolved) {
29227
- runtimeVariables[varName] = resolved.value;
29228
- } else {
29229
- runtimeVariables[varName] = "";
28603
+ if (step.type === "varRequest") {
28604
+ const commandLine = step.content.split("\n")[0];
28605
+ const parsed = parseVarRequestCommand(commandLine);
28606
+ if (!parsed) {
28607
+ errors.push(`Step ${stepIdx + 1}: Invalid variable request: ${commandLine}`);
28608
+ return {
28609
+ name: "",
28610
+ success: false,
28611
+ responses,
28612
+ scriptResults,
28613
+ assertionResults,
28614
+ steps: orderedSteps,
28615
+ errors,
28616
+ duration: Date.now() - startTime
28617
+ };
29230
28618
  }
29231
- } else {
29232
- const returnedObj = {};
29233
- for (const expr of returnExpressions) {
29234
- const resolved = resolveReturnExpression(expr, subResult.variables);
29235
- if (resolved) {
29236
- returnedObj[resolved.key] = resolved.value;
28619
+ requestIndex++;
28620
+ let resolvedUrl = "";
28621
+ let resolvedHeaders = {};
28622
+ let requestDescription = `${parsed.method} ${parsed.url}`;
28623
+ try {
28624
+ const endpointMatch = parsed.url.match(/^([a-zA-Z_][a-zA-Z0-9_]*)(?:\(([^)]*)\))?(\s+.*)?$/);
28625
+ const potentialEndpointName = endpointMatch ? endpointMatch[1] : null;
28626
+ const endpoint = apiDefinitions && potentialEndpointName ? getEndpoint({ headerGroups: apiDefinitions.headerGroups, endpoints: apiDefinitions.endpoints }, potentialEndpointName) : void 0;
28627
+ if (endpoint && apiDefinitions) {
28628
+ const paramsStr = endpointMatch[2] || "";
28629
+ const headerGroupsStr = endpointMatch[3]?.trim() || "";
28630
+ const paramTokens = paramsStr ? paramsStr.split(",").map((p) => p.trim()) : [];
28631
+ const params = {};
28632
+ const isNamedSyntax = paramTokens.length > 0 && paramTokens[0].includes(":");
28633
+ if (isNamedSyntax) {
28634
+ for (const token of paramTokens) {
28635
+ const kvMatch = token.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.+)$/);
28636
+ if (kvMatch) {
28637
+ const paramName = kvMatch[1];
28638
+ let paramValue = kvMatch[2].trim().replace(/^["']|["']$/g, "");
28639
+ const bareResolved = resolveBareVariables(paramValue, runtimeVariables);
28640
+ params[paramName] = substituteVariables(bareResolved, runtimeVariables);
28641
+ }
28642
+ }
28643
+ } else {
28644
+ endpoint.parameters.forEach((paramName, idx) => {
28645
+ if (paramTokens[idx] !== void 0) {
28646
+ let paramValue = paramTokens[idx].replace(/^["']|["']$/g, "");
28647
+ const bareResolved = resolveBareVariables(paramValue, runtimeVariables);
28648
+ params[paramName] = substituteVariables(bareResolved, runtimeVariables);
28649
+ }
28650
+ });
28651
+ }
28652
+ let pathWithVars = substituteVariables(endpoint.path, runtimeVariables);
28653
+ for (const paramName of endpoint.parameters) {
28654
+ const value = params[paramName];
28655
+ if (value !== void 0) {
28656
+ pathWithVars = pathWithVars.replace(`{${paramName}}`, value);
28657
+ }
28658
+ }
28659
+ resolvedUrl = pathWithVars;
28660
+ if (headerGroupsStr) {
28661
+ const headerGroupNames = headerGroupsStr.split(/\s+/).filter((n) => n);
28662
+ for (const groupName of headerGroupNames) {
28663
+ const group = getHeaderGroup(
28664
+ { headerGroups: apiDefinitions.headerGroups, endpoints: apiDefinitions.endpoints },
28665
+ groupName
28666
+ );
28667
+ if (group) {
28668
+ const groupHeaders = resolveHeaderValues(group, runtimeVariables);
28669
+ Object.assign(resolvedHeaders, groupHeaders);
28670
+ }
28671
+ }
28672
+ }
28673
+ requestDescription = `${potentialEndpointName}(${paramTokens.join(", ")}) \u2192 ${parsed.method} ${resolvedUrl}`;
28674
+ } else {
28675
+ resolvedUrl = substituteVariables(parsed.url, runtimeVariables);
28676
+ requestDescription = `${parsed.method} ${resolvedUrl}`;
28677
+ }
28678
+ let requestText = `${parsed.method} ${resolvedUrl}`;
28679
+ if (Object.keys(resolvedHeaders).length > 0) {
28680
+ const headerLines = Object.entries(resolvedHeaders).map(([k, v]) => `${k}: ${v}`);
28681
+ requestText += "\n" + headerLines.join("\n");
28682
+ }
28683
+ const contentLines = step.content.split("\n");
28684
+ if (contentLines.length > 1) {
28685
+ const extraLines = contentLines.slice(1).map((line2) => substituteVariables(line2, runtimeVariables));
28686
+ const hasExplicitBlank = extraLines.some((line2) => line2.trim() === "");
28687
+ const firstNonEmpty = extraLines.find((line2) => line2.trim() !== "");
28688
+ const startsWithHeader = firstNonEmpty ? /^[A-Za-z0-9\-_]+\s*:/.test(firstNonEmpty.trim()) : false;
28689
+ if (hasExplicitBlank || startsWithHeader) {
28690
+ requestText += "\n" + extraLines.join("\n");
28691
+ } else {
28692
+ requestText += "\n\n" + extraLines.join("\n");
28693
+ }
28694
+ }
28695
+ const requestParsed = parserHttpRequest(requestText, runtimeVariables);
28696
+ validatePreparedRequest(
28697
+ requestParsed,
28698
+ buildRequestValidationContext(stepIdx + 1, requestParsed.method, requestParsed.url, `var ${parsed.varName}`)
28699
+ );
28700
+ const retryOpts = parsed.retryCount ? {
28701
+ retryCount: parsed.retryCount,
28702
+ backoffMs: parsed.backoffMs || 1e3,
28703
+ onRetry: (attempt, total, error, waitMs) => {
28704
+ reportProgress(stepIdx, "request", `[Retry ${attempt}/${total}] ${requestDescription} - waiting ${waitMs}ms`, void 0);
28705
+ }
28706
+ } : void 0;
28707
+ const response = cookieJar ? await sendRequestWithJar(requestParsed, cookieJar, retryOpts) : await sendRequest(requestParsed, retryOpts);
28708
+ addResponse(response);
28709
+ responseIndexToVariable.set(responses.length, parsed.varName);
28710
+ runtimeVariables[parsed.varName] = response;
28711
+ const stepResult = {
28712
+ type: "request",
28713
+ stepIndex: stepIdx,
28714
+ response,
28715
+ requestMethod: parsed.method,
28716
+ requestUrl: resolvedUrl,
28717
+ variableName: parsed.varName
28718
+ };
28719
+ orderedSteps.push(stepResult);
28720
+ reportProgress(stepIdx, "request", `var ${parsed.varName} = ${requestDescription}`, stepResult);
28721
+ const relevantCaptures = captures.filter((c) => c.afterRequest === requestIndex);
28722
+ for (const capture of relevantCaptures) {
28723
+ const value = getValueByPath(response, capture.path);
28724
+ if (value !== void 0) {
28725
+ runtimeVariables[capture.varName] = value;
28726
+ } else {
28727
+ errors.push(`Warning: Could not capture ${capture.varName} from $${requestIndex}.${capture.path}`);
28728
+ }
29237
28729
  }
28730
+ } catch (error) {
28731
+ const userMessage = formatUserFacingError(
28732
+ error,
28733
+ buildRequestValidationContext(stepIdx + 1, void 0, void 0, `var ${parsed.varName}`)
28734
+ );
28735
+ let errorDetails = `Request failed for var ${parsed.varName}:
28736
+ ${indentMultiline(userMessage)}`;
28737
+ if (requestDescription && !/\nRequest:\s/.test(userMessage)) {
28738
+ errorDetails += `
28739
+ Request: ${requestDescription}`;
28740
+ }
28741
+ errors.push(errorDetails);
28742
+ await emitFailure(errorDetails, step, stepIdx);
28743
+ return {
28744
+ name: "",
28745
+ success: false,
28746
+ responses,
28747
+ scriptResults,
28748
+ assertionResults,
28749
+ steps: orderedSteps,
28750
+ errors,
28751
+ duration: Date.now() - startTime
28752
+ };
29238
28753
  }
29239
- runtimeVariables[varName] = JSON.stringify(returnedObj);
28754
+ continue;
29240
28755
  }
29241
- } else {
29242
- runtimeVariables[varName] = "";
29243
- }
29244
- requestIndex += subResult.responses.length;
29245
- if (!subResult.success) {
29246
- errors.push(`Sequence "${sequenceName}" failed`);
29247
- errors.push(...subResult.errors);
29248
- return {
29249
- name: "",
29250
- success: false,
29251
- responses,
29252
- scriptResults,
29253
- assertionResults,
29254
- steps: orderedSteps,
29255
- errors,
29256
- duration: Date.now() - startTime,
29257
- variables: runtimeVariables
29258
- };
29259
- }
29260
- continue;
29261
- } else {
29262
- requestIndex++;
29263
- let parsed;
29264
- let requestDescription = "";
29265
- try {
29266
- if (apiDefinitions && apiDefinitions.endpoints.length > 0 && isApiRequestLine(step.content, apiDefinitions.endpoints)) {
29267
- const apiRequest = parseApiRequest(
29268
- step.content,
29269
- apiDefinitions.endpoints,
29270
- apiDefinitions.headerGroups
29271
- );
29272
- if (apiRequest) {
29273
- const endpoint = getEndpoint(
29274
- { headerGroups: apiDefinitions.headerGroups, endpoints: apiDefinitions.endpoints },
29275
- apiRequest.endpointName
28756
+ if (step.type === "script") {
28757
+ const parsed = parseRunCommand(substituteVariables(step.content, runtimeVariables));
28758
+ if (!parsed) {
28759
+ errors.push(`Step ${stepIdx + 1}: Invalid run command: ${step.content}`);
28760
+ return {
28761
+ name: "",
28762
+ success: false,
28763
+ responses,
28764
+ scriptResults,
28765
+ assertionResults,
28766
+ steps: orderedSteps,
28767
+ errors,
28768
+ duration: Date.now() - startTime
28769
+ };
28770
+ }
28771
+ const substitutedArgs = parsed.args.map((arg) => {
28772
+ const bareResolved = resolveBareVariables(arg, runtimeVariables);
28773
+ return substituteVariables(bareResolved, runtimeVariables);
28774
+ });
28775
+ const substitutedPath = substituteVariables(parsed.scriptPath, runtimeVariables);
28776
+ try {
28777
+ const result = await runScript(
28778
+ parsed.type,
28779
+ substitutedPath,
28780
+ substitutedArgs,
28781
+ workingDir || process.cwd(),
28782
+ runtimeVariables,
28783
+ parsed.captureAs
29276
28784
  );
29277
- if (!endpoint) {
29278
- throw new Error(`Unknown endpoint: ${apiRequest.endpointName}`);
29279
- }
29280
- const pathWithVars = substituteVariables(endpoint.path, runtimeVariables);
29281
- const resolvedParams = {};
29282
- for (const [key, value] of Object.entries(apiRequest.params)) {
29283
- const bareResolved = resolveBareVariables(value, runtimeVariables);
29284
- resolvedParams[key] = substituteVariables(bareResolved, runtimeVariables);
28785
+ scriptResults.push(result);
28786
+ const stepResult = {
28787
+ type: "script",
28788
+ stepIndex: stepIdx,
28789
+ script: result
28790
+ };
28791
+ orderedSteps.push(stepResult);
28792
+ reportProgress(stepIdx, "script", `run ${parsed.type} ${substitutedPath}`, stepResult);
28793
+ if (!result.success) {
28794
+ errors.push(`Script failed (exit ${result.exitCode}): ${result.error || result.output}`);
28795
+ return {
28796
+ name: "",
28797
+ success: false,
28798
+ responses,
28799
+ scriptResults,
28800
+ assertionResults,
28801
+ steps: orderedSteps,
28802
+ errors,
28803
+ duration: Date.now() - startTime
28804
+ };
29285
28805
  }
29286
- let resolvedPath = pathWithVars;
29287
- for (const paramName of endpoint.parameters) {
29288
- const value = resolvedParams[paramName];
29289
- if (value !== void 0) {
29290
- resolvedPath = resolvedPath.replace(`{${paramName}}`, value);
28806
+ if (parsed.captureAs) {
28807
+ let outputValue = result.output;
28808
+ try {
28809
+ const jsonParsed = JSON.parse(result.output);
28810
+ outputValue = jsonParsed;
28811
+ } catch {
29291
28812
  }
28813
+ runtimeVariables[parsed.captureAs] = outputValue;
29292
28814
  }
29293
- const combinedHeaders = {};
29294
- for (const groupName of apiRequest.headerGroupNames) {
29295
- const group = getHeaderGroup(
29296
- { headerGroups: apiDefinitions.headerGroups, endpoints: apiDefinitions.endpoints },
29297
- groupName
29298
- );
29299
- if (group) {
29300
- const resolvedHeaders = resolveHeaderValues(group, runtimeVariables);
29301
- Object.assign(combinedHeaders, resolvedHeaders);
28815
+ } catch (error) {
28816
+ errors.push(`Script execution error: ${error.message}`);
28817
+ return {
28818
+ name: "",
28819
+ success: false,
28820
+ responses,
28821
+ scriptResults,
28822
+ assertionResults,
28823
+ steps: orderedSteps,
28824
+ errors,
28825
+ duration: Date.now() - startTime
28826
+ };
28827
+ }
28828
+ } else if (step.type === "namedRequest") {
28829
+ const parsed = parseRunNamedRequestCommand(step.content);
28830
+ if (!parsed) {
28831
+ errors.push(`Invalid run command: ${step.content}`);
28832
+ return {
28833
+ name: "",
28834
+ success: false,
28835
+ responses,
28836
+ scriptResults,
28837
+ assertionResults,
28838
+ steps: orderedSteps,
28839
+ errors,
28840
+ duration: Date.now() - startTime
28841
+ };
28842
+ }
28843
+ const { name: targetName, args: runArgs } = parsed;
28844
+ if (!fullDocumentText) {
28845
+ errors.push(`Cannot run "${targetName}": full document text not provided`);
28846
+ return {
28847
+ name: "",
28848
+ success: false,
28849
+ responses,
28850
+ scriptResults,
28851
+ assertionResults,
28852
+ steps: orderedSteps,
28853
+ errors,
28854
+ duration: Date.now() - startTime
28855
+ };
28856
+ }
28857
+ const allSequences = extractSequences(fullDocumentText);
28858
+ const targetSequence = allSequences.find((s) => s.name === targetName);
28859
+ if (targetSequence) {
28860
+ const currentCallStack = callStack || [];
28861
+ if (currentCallStack.includes(targetName)) {
28862
+ errors.push(`Circular reference detected: ${[...currentCallStack, targetName].join(" \u2192 ")}`);
28863
+ return {
28864
+ name: "",
28865
+ success: false,
28866
+ responses,
28867
+ scriptResults,
28868
+ assertionResults,
28869
+ steps: orderedSteps,
28870
+ errors,
28871
+ duration: Date.now() - startTime
28872
+ };
28873
+ }
28874
+ let sequenceArgs2 = {};
28875
+ if (targetSequence.parameters.length > 0 || runArgs.length > 0) {
28876
+ const bindResult = bindSequenceArguments(targetSequence.parameters, runArgs, runtimeVariables);
28877
+ if ("error" in bindResult) {
28878
+ errors.push(`Error calling sequence "${targetName}": ${bindResult.error}`);
28879
+ return {
28880
+ name: "",
28881
+ success: false,
28882
+ responses,
28883
+ scriptResults,
28884
+ assertionResults,
28885
+ steps: orderedSteps,
28886
+ errors,
28887
+ duration: Date.now() - startTime
28888
+ };
29302
28889
  }
28890
+ sequenceArgs2 = bindResult.variables;
29303
28891
  }
29304
- for (const [headerName, headerValue] of Object.entries(apiRequest.inlineHeaders)) {
29305
- combinedHeaders[headerName] = substituteVariables(headerValue, runtimeVariables);
28892
+ if (tagFilterOptions && tagFilterOptions.filters.length > 0) {
28893
+ if (!sequenceMatchesTags(targetSequence, tagFilterOptions)) {
28894
+ continue;
28895
+ }
29306
28896
  }
29307
- let resolvedBody;
29308
- if (apiRequest.body) {
29309
- resolvedBody = substituteVariables(apiRequest.body, runtimeVariables);
28897
+ reportProgress(stepIdx, "sequenceStart", `Starting sequence ${targetName}`, void 0, targetName);
28898
+ let subWorkingDir = workingDir;
28899
+ if (sequenceSources) {
28900
+ const sourceFile = sequenceSources.get(targetName.toLowerCase());
28901
+ if (sourceFile) {
28902
+ const path10 = await import("path");
28903
+ subWorkingDir = path10.dirname(sourceFile);
28904
+ }
29310
28905
  }
29311
- parsed = {
29312
- method: apiRequest.method,
29313
- url: resolvedPath,
29314
- headers: combinedHeaders,
29315
- body: resolvedBody
28906
+ const targetLocation = executionContext?.sequenceLocationIndex?.get(targetName.toLowerCase());
28907
+ const childExecutionContext = {
28908
+ filePath: targetLocation?.filePath ?? executionContext?.filePath,
28909
+ sequenceName: targetName,
28910
+ environment: executionContext?.environment,
28911
+ sequenceStartLine: targetLocation?.startLine ?? targetSequence.startLine,
28912
+ sequenceLocationIndex: executionContext?.sequenceLocationIndex
29316
28913
  };
29317
- requestDescription = `${apiRequest.endpointName}(${Object.values(resolvedParams).join(", ")}) \u2192 ${parsed.method} ${parsed.url}`;
29318
- } else {
29319
- parsed = parserHttpRequest(step.content, runtimeVariables);
29320
- requestDescription = `${parsed.method} ${parsed.url}`;
29321
- if (apiDefinitions && apiDefinitions.headerGroups.length > 0) {
29322
- parsed = applyHeaderGroupsToRequest(parsed, step.content, apiDefinitions.headerGroups, runtimeVariables);
28914
+ const subResult = await runSequenceWithJar(
28915
+ targetSequence.content,
28916
+ runtimeVariables,
28917
+ // Pass current runtime variables (includes file vars + any captured)
28918
+ cookieJar,
28919
+ subWorkingDir,
28920
+ fullDocumentText,
28921
+ onProgress,
28922
+ // Pass through progress callback
28923
+ [...currentCallStack, targetName],
28924
+ // Add to call stack for circular detection
28925
+ sequenceArgs2,
28926
+ // Pass bound arguments as initial scope variables
28927
+ apiDefinitions,
28928
+ // Pass API definitions for endpoint resolution
28929
+ tagFilterOptions,
28930
+ // Pass tag filter options for nested sequence filtering
28931
+ sequenceSources,
28932
+ // Pass sequence sources for nested sequences
28933
+ childExecutionContext,
28934
+ debugHooks
28935
+ );
28936
+ reportProgress(stepIdx, "sequenceEnd", `Finished sequence ${targetName}`, void 0, targetName);
28937
+ for (const subResponse of subResult.responses) {
28938
+ addResponse(subResponse);
28939
+ }
28940
+ scriptResults.push(...subResult.scriptResults);
28941
+ assertionResults.push(...subResult.assertionResults);
28942
+ for (const subStep of subResult.steps) {
28943
+ orderedSteps.push({
28944
+ ...subStep,
28945
+ stepIndex: orderedSteps.length
28946
+ });
28947
+ }
28948
+ requestIndex += subResult.responses.length;
28949
+ if (!subResult.success) {
28950
+ errors.push(`Sequence "${targetName}" failed`);
28951
+ errors.push(...subResult.errors);
28952
+ return {
28953
+ name: "",
28954
+ success: false,
28955
+ responses,
28956
+ scriptResults,
28957
+ assertionResults,
28958
+ steps: orderedSteps,
28959
+ errors,
28960
+ duration: Date.now() - startTime,
28961
+ variables: runtimeVariables
28962
+ };
29323
28963
  }
28964
+ continue;
29324
28965
  }
29325
- } else {
29326
- parsed = parserHttpRequest(step.content, runtimeVariables);
29327
- requestDescription = `${parsed.method} ${parsed.url}`;
29328
- if (apiDefinitions && apiDefinitions.headerGroups.length > 0) {
29329
- parsed = applyHeaderGroupsToRequest(parsed, step.content, apiDefinitions.headerGroups, runtimeVariables);
28966
+ const namedRequest = getNamedRequest(fullDocumentText, targetName);
28967
+ if (!namedRequest) {
28968
+ errors.push(`"${targetName}" is not a named request or sequence`);
28969
+ return {
28970
+ name: "",
28971
+ success: false,
28972
+ responses,
28973
+ scriptResults,
28974
+ assertionResults,
28975
+ steps: orderedSteps,
28976
+ errors,
28977
+ duration: Date.now() - startTime
28978
+ };
29330
28979
  }
29331
- if (parsed.body) {
29332
- const bodyTrimmed = parsed.body.trim();
29333
- const bareVarMatch = bodyTrimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)$/);
29334
- if (bareVarMatch && bareVarMatch[1] in runtimeVariables) {
29335
- const varValue = runtimeVariables[bareVarMatch[1]];
29336
- if (typeof varValue === "object" && varValue !== null) {
29337
- parsed.body = JSON.stringify(varValue);
29338
- } else if (typeof varValue === "string") {
29339
- parsed.body = varValue;
28980
+ requestIndex++;
28981
+ let requestUrl = "";
28982
+ let requestMethod = "";
28983
+ try {
28984
+ let requestParsed;
28985
+ if (apiDefinitions && apiDefinitions.endpoints.length > 0 && isApiRequestLine(namedRequest.content, apiDefinitions.endpoints)) {
28986
+ const apiRequest = parseApiRequest(
28987
+ namedRequest.content,
28988
+ apiDefinitions.endpoints,
28989
+ apiDefinitions.headerGroups
28990
+ );
28991
+ if (apiRequest) {
28992
+ const endpoint = getEndpoint(
28993
+ { headerGroups: apiDefinitions.headerGroups, endpoints: apiDefinitions.endpoints },
28994
+ apiRequest.endpointName
28995
+ );
28996
+ if (endpoint) {
28997
+ let resolvedPath = endpoint.path;
28998
+ resolvedPath = resolvedPath.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (_, varName) => {
28999
+ return runtimeVariables[varName] !== void 0 ? String(runtimeVariables[varName]) : `{{${varName}}}`;
29000
+ });
29001
+ for (const [paramName, paramValue] of Object.entries(apiRequest.params)) {
29002
+ let resolvedValue = paramValue;
29003
+ if (runtimeVariables[paramValue] !== void 0) {
29004
+ resolvedValue = String(runtimeVariables[paramValue]);
29005
+ } else if (paramValue.startsWith("{{") && paramValue.endsWith("}}")) {
29006
+ const varName = paramValue.slice(2, -2);
29007
+ if (runtimeVariables[varName] !== void 0) {
29008
+ resolvedValue = String(runtimeVariables[varName]);
29009
+ }
29010
+ }
29011
+ resolvedPath = resolvedPath.replace(`{${paramName}}`, resolvedValue);
29012
+ }
29013
+ const combinedHeaders = {};
29014
+ for (const groupName of apiRequest.headerGroupNames) {
29015
+ const group = getHeaderGroup(apiDefinitions, groupName);
29016
+ if (group) {
29017
+ const resolvedHeaders = resolveHeaderValues(group, runtimeVariables);
29018
+ Object.assign(combinedHeaders, resolvedHeaders);
29019
+ }
29020
+ }
29021
+ for (const [headerName, headerValue] of Object.entries(apiRequest.inlineHeaders)) {
29022
+ let resolved = headerValue;
29023
+ resolved = resolved.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (_, varName) => {
29024
+ return runtimeVariables[varName] !== void 0 ? String(runtimeVariables[varName]) : `{{${varName}}}`;
29025
+ });
29026
+ combinedHeaders[headerName] = resolved;
29027
+ }
29028
+ const bodyParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
29029
+ requestParsed = {
29030
+ method: apiRequest.method,
29031
+ url: resolvedPath,
29032
+ headers: combinedHeaders,
29033
+ body: bodyParsed.body
29034
+ };
29035
+ } else {
29036
+ throw new Error(`Unknown endpoint: ${apiRequest.endpointName}`);
29037
+ }
29038
+ } else {
29039
+ requestParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
29040
+ }
29041
+ } else {
29042
+ requestParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
29043
+ if (apiDefinitions && apiDefinitions.headerGroups.length > 0) {
29044
+ requestParsed = applyHeaderGroupsToRequest(requestParsed, namedRequest.content, apiDefinitions.headerGroups, runtimeVariables);
29045
+ }
29046
+ }
29047
+ requestUrl = requestParsed.url;
29048
+ requestMethod = requestParsed.method;
29049
+ validatePreparedRequest(
29050
+ requestParsed,
29051
+ buildRequestValidationContext(stepIdx + 1, requestMethod, requestUrl, targetName)
29052
+ );
29053
+ const effectiveRetryCount = parsed.retryCount ?? requestParsed.retryCount;
29054
+ const effectiveBackoffMs = parsed.backoffMs ?? requestParsed.backoffMs ?? 1e3;
29055
+ const retryOpts = effectiveRetryCount ? {
29056
+ retryCount: effectiveRetryCount,
29057
+ backoffMs: effectiveBackoffMs,
29058
+ onRetry: (attempt, total, error, waitMs) => {
29059
+ reportProgress(stepIdx, "request", `[Retry ${attempt}/${total}] run ${targetName} - waiting ${waitMs}ms`, void 0);
29060
+ }
29061
+ } : void 0;
29062
+ const response = cookieJar ? await sendRequestWithJar(requestParsed, cookieJar, retryOpts) : await sendRequest(requestParsed, retryOpts);
29063
+ addResponse(response);
29064
+ const stepResult = {
29065
+ type: "request",
29066
+ stepIndex: stepIdx,
29067
+ response,
29068
+ requestMethod: requestParsed.method,
29069
+ requestUrl: requestParsed.url
29070
+ };
29071
+ orderedSteps.push(stepResult);
29072
+ reportProgress(stepIdx, "namedRequest", `run ${targetName} \u2192 ${requestParsed.method} ${requestParsed.url}`, stepResult);
29073
+ const relevantCaptures = captures.filter((c) => c.afterRequest === requestIndex);
29074
+ for (const capture of relevantCaptures) {
29075
+ const value = getValueByPath(response, capture.path);
29076
+ if (value !== void 0) {
29077
+ runtimeVariables[capture.varName] = value;
29340
29078
  } else {
29341
- parsed.body = String(varValue);
29079
+ errors.push(`Warning: Could not capture ${capture.varName} from $${requestIndex}.${capture.path}`);
29342
29080
  }
29343
29081
  }
29082
+ } catch (error) {
29083
+ const userMessage = formatUserFacingError(
29084
+ error,
29085
+ buildRequestValidationContext(stepIdx + 1, requestMethod || void 0, requestUrl || void 0, targetName)
29086
+ );
29087
+ let errorDetails = `Named request "${targetName}" failed:
29088
+ ${indentMultiline(userMessage)}`;
29089
+ if (requestUrl && !/\nRequest:\s/.test(userMessage)) {
29090
+ errorDetails += `
29091
+ Request: ${requestMethod} ${requestUrl}`;
29092
+ }
29093
+ errors.push(errorDetails);
29094
+ await emitFailure(errorDetails, step, stepIdx);
29095
+ return {
29096
+ name: "",
29097
+ success: false,
29098
+ responses,
29099
+ scriptResults,
29100
+ assertionResults,
29101
+ steps: orderedSteps,
29102
+ errors,
29103
+ duration: Date.now() - startTime
29104
+ };
29344
29105
  }
29345
- }
29346
- const { retryCount, backoffMs } = extractRetryOptions(step.content);
29347
- validatePreparedRequest(
29348
- parsed,
29349
- buildRequestValidationContext(stepIdx + 1, parsed.method, parsed.url)
29350
- );
29351
- const retryOpts = retryCount ?? parsed.retryCount ? {
29352
- retryCount: retryCount ?? parsed.retryCount ?? 0,
29353
- backoffMs: backoffMs ?? parsed.backoffMs ?? 1e3,
29354
- onRetry: (attempt, total, error, waitMs) => {
29355
- reportProgress(stepIdx, "request", `[Retry ${attempt}/${total}] ${requestDescription} - waiting ${waitMs}ms`, void 0);
29106
+ } else if (step.type === "varRunSequence") {
29107
+ const parsed = parseVarRunSequenceCommand(step.content);
29108
+ if (!parsed) {
29109
+ errors.push(`Invalid var run sequence command: ${step.content}`);
29110
+ return {
29111
+ name: "",
29112
+ success: false,
29113
+ responses,
29114
+ scriptResults,
29115
+ assertionResults,
29116
+ steps: orderedSteps,
29117
+ errors,
29118
+ duration: Date.now() - startTime
29119
+ };
29356
29120
  }
29357
- } : void 0;
29358
- const response = cookieJar ? await sendRequestWithJar(parsed, cookieJar, retryOpts) : await sendRequest(parsed, retryOpts);
29359
- addResponse(response);
29360
- const stepResult = {
29361
- type: "request",
29362
- stepIndex: stepIdx,
29363
- response,
29364
- requestMethod: parsed.method,
29365
- requestUrl: parsed.url
29366
- };
29367
- orderedSteps.push(stepResult);
29368
- reportProgress(stepIdx, "request", requestDescription, stepResult);
29369
- const relevantCaptures = captures.filter((c) => c.afterRequest === requestIndex);
29370
- for (const capture of relevantCaptures) {
29371
- const value = getValueByPath(response, capture.path);
29372
- if (value !== void 0) {
29373
- runtimeVariables[capture.varName] = value;
29121
+ const { varName, sequenceName, args: runArgs } = parsed;
29122
+ if (!fullDocumentText) {
29123
+ errors.push(`Cannot run sequence "${sequenceName}": full document text not provided`);
29124
+ return {
29125
+ name: "",
29126
+ success: false,
29127
+ responses,
29128
+ scriptResults,
29129
+ assertionResults,
29130
+ steps: orderedSteps,
29131
+ errors,
29132
+ duration: Date.now() - startTime
29133
+ };
29134
+ }
29135
+ const allSequences = extractSequences(fullDocumentText);
29136
+ const targetSequence = allSequences.find((s) => s.name === sequenceName);
29137
+ if (!targetSequence) {
29138
+ const namedRequest = getNamedRequest(fullDocumentText, sequenceName);
29139
+ if (!namedRequest) {
29140
+ errors.push(`"${sequenceName}" is not a named request or sequence`);
29141
+ return {
29142
+ name: "",
29143
+ success: false,
29144
+ responses,
29145
+ scriptResults,
29146
+ assertionResults,
29147
+ steps: orderedSteps,
29148
+ errors,
29149
+ duration: Date.now() - startTime
29150
+ };
29151
+ }
29152
+ requestIndex++;
29153
+ let requestUrl = "";
29154
+ let requestMethod = "";
29155
+ try {
29156
+ const requestParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
29157
+ requestUrl = requestParsed.url;
29158
+ requestMethod = requestParsed.method;
29159
+ validatePreparedRequest(
29160
+ requestParsed,
29161
+ buildRequestValidationContext(stepIdx + 1, requestMethod, requestUrl, `${varName} = run ${sequenceName}`)
29162
+ );
29163
+ const effectiveRetryCount = parsed.retryCount ?? requestParsed.retryCount;
29164
+ const effectiveBackoffMs = parsed.backoffMs ?? requestParsed.backoffMs ?? 1e3;
29165
+ const retryOpts = effectiveRetryCount ? {
29166
+ retryCount: effectiveRetryCount,
29167
+ backoffMs: effectiveBackoffMs,
29168
+ onRetry: (attempt, total, error, waitMs) => {
29169
+ reportProgress(stepIdx, "request", `[Retry ${attempt}/${total}] var ${varName} = run ${sequenceName} - waiting ${waitMs}ms`, void 0);
29170
+ }
29171
+ } : void 0;
29172
+ const response = cookieJar ? await sendRequestWithJar(requestParsed, cookieJar, retryOpts) : await sendRequest(requestParsed, retryOpts);
29173
+ addResponse(response);
29174
+ responseIndexToVariable.set(responses.length, varName);
29175
+ const stepResult = {
29176
+ type: "request",
29177
+ stepIndex: stepIdx,
29178
+ response,
29179
+ requestMethod: requestParsed.method,
29180
+ requestUrl: requestParsed.url,
29181
+ variableName: varName
29182
+ };
29183
+ orderedSteps.push(stepResult);
29184
+ reportProgress(stepIdx, "namedRequest", `var ${varName} = run ${sequenceName} \u2192 ${requestParsed.method} ${requestParsed.url}`, stepResult);
29185
+ const capturedResponse = {
29186
+ status: response.status,
29187
+ statusText: response.statusText,
29188
+ body: response.body,
29189
+ headers: response.headers
29190
+ };
29191
+ runtimeVariables[varName] = JSON.stringify(capturedResponse);
29192
+ const relevantCaptures = captures.filter((c) => c.afterRequest === requestIndex);
29193
+ for (const capture of relevantCaptures) {
29194
+ const value = getValueByPath(response, capture.path);
29195
+ if (value !== void 0) {
29196
+ runtimeVariables[capture.varName] = value;
29197
+ } else {
29198
+ errors.push(`Warning: Could not capture ${capture.varName} from $${requestIndex}.${capture.path}`);
29199
+ }
29200
+ }
29201
+ } catch (error) {
29202
+ const userMessage = formatUserFacingError(
29203
+ error,
29204
+ buildRequestValidationContext(stepIdx + 1, requestMethod || void 0, requestUrl || void 0, `${varName} = run ${sequenceName}`)
29205
+ );
29206
+ let errorDetails = `Named request "${sequenceName}" failed:
29207
+ ${indentMultiline(userMessage)}`;
29208
+ if (requestUrl && !/\nRequest:\s/.test(userMessage)) {
29209
+ errorDetails += `
29210
+ Request: ${requestMethod} ${requestUrl}`;
29211
+ }
29212
+ errors.push(errorDetails);
29213
+ await emitFailure(errorDetails, step, stepIdx);
29214
+ return {
29215
+ name: "",
29216
+ success: false,
29217
+ responses,
29218
+ scriptResults,
29219
+ assertionResults,
29220
+ steps: orderedSteps,
29221
+ errors,
29222
+ duration: Date.now() - startTime
29223
+ };
29224
+ }
29225
+ continue;
29226
+ }
29227
+ const currentCallStack = callStack || [];
29228
+ if (currentCallStack.includes(sequenceName)) {
29229
+ errors.push(`Circular reference detected: ${[...currentCallStack, sequenceName].join(" \u2192 ")}`);
29230
+ return {
29231
+ name: "",
29232
+ success: false,
29233
+ responses,
29234
+ scriptResults,
29235
+ assertionResults,
29236
+ steps: orderedSteps,
29237
+ errors,
29238
+ duration: Date.now() - startTime
29239
+ };
29240
+ }
29241
+ let sequenceArgs2 = {};
29242
+ if (targetSequence.parameters.length > 0 || runArgs.length > 0) {
29243
+ const bindResult = bindSequenceArguments(targetSequence.parameters, runArgs, runtimeVariables);
29244
+ if ("error" in bindResult) {
29245
+ errors.push(`Error calling sequence "${sequenceName}": ${bindResult.error}`);
29246
+ return {
29247
+ name: "",
29248
+ success: false,
29249
+ responses,
29250
+ scriptResults,
29251
+ assertionResults,
29252
+ steps: orderedSteps,
29253
+ errors,
29254
+ duration: Date.now() - startTime
29255
+ };
29256
+ }
29257
+ sequenceArgs2 = bindResult.variables;
29258
+ }
29259
+ if (tagFilterOptions && tagFilterOptions.filters.length > 0) {
29260
+ if (!sequenceMatchesTags(targetSequence, tagFilterOptions)) {
29261
+ continue;
29262
+ }
29263
+ }
29264
+ reportProgress(stepIdx, "sequenceStart", `Starting sequence ${sequenceName}`, void 0, sequenceName);
29265
+ let subWorkingDir = workingDir;
29266
+ if (sequenceSources) {
29267
+ const sourceFile = sequenceSources.get(sequenceName.toLowerCase());
29268
+ if (sourceFile) {
29269
+ const path10 = await import("path");
29270
+ subWorkingDir = path10.dirname(sourceFile);
29271
+ }
29272
+ }
29273
+ const targetLocation = executionContext?.sequenceLocationIndex?.get(sequenceName.toLowerCase());
29274
+ const childExecutionContext = {
29275
+ filePath: targetLocation?.filePath ?? executionContext?.filePath,
29276
+ sequenceName,
29277
+ environment: executionContext?.environment,
29278
+ sequenceStartLine: targetLocation?.startLine ?? targetSequence.startLine,
29279
+ sequenceLocationIndex: executionContext?.sequenceLocationIndex
29280
+ };
29281
+ const subResult = await runSequenceWithJar(
29282
+ targetSequence.content,
29283
+ runtimeVariables,
29284
+ cookieJar,
29285
+ subWorkingDir,
29286
+ fullDocumentText,
29287
+ onProgress,
29288
+ [...currentCallStack, sequenceName],
29289
+ sequenceArgs2,
29290
+ // Pass bound arguments
29291
+ apiDefinitions,
29292
+ // Pass API definitions for endpoint resolution
29293
+ tagFilterOptions,
29294
+ // Pass tag filter options for nested sequence filtering
29295
+ sequenceSources,
29296
+ // Pass sequence sources for nested sequences
29297
+ childExecutionContext,
29298
+ debugHooks
29299
+ );
29300
+ reportProgress(stepIdx, "sequenceEnd", `Finished sequence ${sequenceName}`, void 0, sequenceName);
29301
+ for (const subResponse of subResult.responses) {
29302
+ addResponse(subResponse);
29303
+ }
29304
+ scriptResults.push(...subResult.scriptResults);
29305
+ assertionResults.push(...subResult.assertionResults);
29306
+ for (const subStep of subResult.steps) {
29307
+ orderedSteps.push({
29308
+ ...subStep,
29309
+ stepIndex: orderedSteps.length
29310
+ });
29311
+ }
29312
+ const returnExpressions = extractReturnVariables(targetSequence.content);
29313
+ if (returnExpressions && returnExpressions.length > 0 && subResult.variables) {
29314
+ if (returnExpressions.length === 1) {
29315
+ const resolved = resolveReturnExpression(returnExpressions[0], subResult.variables);
29316
+ if (resolved) {
29317
+ runtimeVariables[varName] = resolved.value;
29318
+ } else {
29319
+ runtimeVariables[varName] = "";
29320
+ }
29321
+ } else {
29322
+ const returnedObj = {};
29323
+ for (const expr of returnExpressions) {
29324
+ const resolved = resolveReturnExpression(expr, subResult.variables);
29325
+ if (resolved) {
29326
+ returnedObj[resolved.key] = resolved.value;
29327
+ }
29328
+ }
29329
+ runtimeVariables[varName] = JSON.stringify(returnedObj);
29330
+ }
29374
29331
  } else {
29375
- errors.push(`Warning: Could not capture ${capture.varName} from $${requestIndex}.${capture.path}`);
29332
+ runtimeVariables[varName] = "";
29376
29333
  }
29377
- }
29378
- } catch (error) {
29379
- const userMessage = formatUserFacingError(
29380
- error,
29381
- buildRequestValidationContext(stepIdx + 1)
29382
- );
29383
- let errorDetails = `Request ${requestIndex} failed:
29334
+ requestIndex += subResult.responses.length;
29335
+ if (!subResult.success) {
29336
+ errors.push(`Sequence "${sequenceName}" failed`);
29337
+ errors.push(...subResult.errors);
29338
+ return {
29339
+ name: "",
29340
+ success: false,
29341
+ responses,
29342
+ scriptResults,
29343
+ assertionResults,
29344
+ steps: orderedSteps,
29345
+ errors,
29346
+ duration: Date.now() - startTime,
29347
+ variables: runtimeVariables
29348
+ };
29349
+ }
29350
+ continue;
29351
+ } else {
29352
+ requestIndex++;
29353
+ let parsed;
29354
+ let requestDescription = "";
29355
+ try {
29356
+ if (apiDefinitions && apiDefinitions.endpoints.length > 0 && isApiRequestLine(step.content, apiDefinitions.endpoints)) {
29357
+ const apiRequest = parseApiRequest(
29358
+ step.content,
29359
+ apiDefinitions.endpoints,
29360
+ apiDefinitions.headerGroups
29361
+ );
29362
+ if (apiRequest) {
29363
+ const endpoint = getEndpoint(
29364
+ { headerGroups: apiDefinitions.headerGroups, endpoints: apiDefinitions.endpoints },
29365
+ apiRequest.endpointName
29366
+ );
29367
+ if (!endpoint) {
29368
+ throw new Error(`Unknown endpoint: ${apiRequest.endpointName}`);
29369
+ }
29370
+ const pathWithVars = substituteVariables(endpoint.path, runtimeVariables);
29371
+ const resolvedParams = {};
29372
+ for (const [key, value] of Object.entries(apiRequest.params)) {
29373
+ const bareResolved = resolveBareVariables(value, runtimeVariables);
29374
+ resolvedParams[key] = substituteVariables(bareResolved, runtimeVariables);
29375
+ }
29376
+ let resolvedPath = pathWithVars;
29377
+ for (const paramName of endpoint.parameters) {
29378
+ const value = resolvedParams[paramName];
29379
+ if (value !== void 0) {
29380
+ resolvedPath = resolvedPath.replace(`{${paramName}}`, value);
29381
+ }
29382
+ }
29383
+ const combinedHeaders = {};
29384
+ for (const groupName of apiRequest.headerGroupNames) {
29385
+ const group = getHeaderGroup(
29386
+ { headerGroups: apiDefinitions.headerGroups, endpoints: apiDefinitions.endpoints },
29387
+ groupName
29388
+ );
29389
+ if (group) {
29390
+ const resolvedHeaders = resolveHeaderValues(group, runtimeVariables);
29391
+ Object.assign(combinedHeaders, resolvedHeaders);
29392
+ }
29393
+ }
29394
+ for (const [headerName, headerValue] of Object.entries(apiRequest.inlineHeaders)) {
29395
+ combinedHeaders[headerName] = substituteVariables(headerValue, runtimeVariables);
29396
+ }
29397
+ let resolvedBody;
29398
+ if (apiRequest.body) {
29399
+ resolvedBody = substituteVariables(apiRequest.body, runtimeVariables);
29400
+ }
29401
+ parsed = {
29402
+ method: apiRequest.method,
29403
+ url: resolvedPath,
29404
+ headers: combinedHeaders,
29405
+ body: resolvedBody
29406
+ };
29407
+ requestDescription = `${apiRequest.endpointName}(${Object.values(resolvedParams).join(", ")}) \u2192 ${parsed.method} ${parsed.url}`;
29408
+ } else {
29409
+ parsed = parserHttpRequest(step.content, runtimeVariables);
29410
+ requestDescription = `${parsed.method} ${parsed.url}`;
29411
+ if (apiDefinitions && apiDefinitions.headerGroups.length > 0) {
29412
+ parsed = applyHeaderGroupsToRequest(parsed, step.content, apiDefinitions.headerGroups, runtimeVariables);
29413
+ }
29414
+ }
29415
+ } else {
29416
+ parsed = parserHttpRequest(step.content, runtimeVariables);
29417
+ requestDescription = `${parsed.method} ${parsed.url}`;
29418
+ if (apiDefinitions && apiDefinitions.headerGroups.length > 0) {
29419
+ parsed = applyHeaderGroupsToRequest(parsed, step.content, apiDefinitions.headerGroups, runtimeVariables);
29420
+ }
29421
+ if (parsed.body) {
29422
+ const bodyTrimmed = parsed.body.trim();
29423
+ const bareVarMatch = bodyTrimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)$/);
29424
+ if (bareVarMatch && bareVarMatch[1] in runtimeVariables) {
29425
+ const varValue = runtimeVariables[bareVarMatch[1]];
29426
+ if (typeof varValue === "object" && varValue !== null) {
29427
+ parsed.body = JSON.stringify(varValue);
29428
+ } else if (typeof varValue === "string") {
29429
+ parsed.body = varValue;
29430
+ } else {
29431
+ parsed.body = String(varValue);
29432
+ }
29433
+ }
29434
+ }
29435
+ }
29436
+ const { retryCount, backoffMs } = extractRetryOptions(step.content);
29437
+ validatePreparedRequest(
29438
+ parsed,
29439
+ buildRequestValidationContext(stepIdx + 1, parsed.method, parsed.url)
29440
+ );
29441
+ const retryOpts = retryCount ?? parsed.retryCount ? {
29442
+ retryCount: retryCount ?? parsed.retryCount ?? 0,
29443
+ backoffMs: backoffMs ?? parsed.backoffMs ?? 1e3,
29444
+ onRetry: (attempt, total, error, waitMs) => {
29445
+ reportProgress(stepIdx, "request", `[Retry ${attempt}/${total}] ${requestDescription} - waiting ${waitMs}ms`, void 0);
29446
+ }
29447
+ } : void 0;
29448
+ const response = cookieJar ? await sendRequestWithJar(parsed, cookieJar, retryOpts) : await sendRequest(parsed, retryOpts);
29449
+ addResponse(response);
29450
+ const stepResult = {
29451
+ type: "request",
29452
+ stepIndex: stepIdx,
29453
+ response,
29454
+ requestMethod: parsed.method,
29455
+ requestUrl: parsed.url
29456
+ };
29457
+ orderedSteps.push(stepResult);
29458
+ reportProgress(stepIdx, "request", requestDescription, stepResult);
29459
+ const relevantCaptures = captures.filter((c) => c.afterRequest === requestIndex);
29460
+ for (const capture of relevantCaptures) {
29461
+ const value = getValueByPath(response, capture.path);
29462
+ if (value !== void 0) {
29463
+ runtimeVariables[capture.varName] = value;
29464
+ } else {
29465
+ errors.push(`Warning: Could not capture ${capture.varName} from $${requestIndex}.${capture.path}`);
29466
+ }
29467
+ }
29468
+ } catch (error) {
29469
+ const userMessage = formatUserFacingError(
29470
+ error,
29471
+ buildRequestValidationContext(stepIdx + 1)
29472
+ );
29473
+ let errorDetails = `Request ${requestIndex} failed:
29384
29474
  ${indentMultiline(userMessage)}`;
29385
- if (requestDescription && !/\nRequest:\s/.test(userMessage)) {
29386
- errorDetails += `
29475
+ if (requestDescription && !/\nRequest:\s/.test(userMessage)) {
29476
+ errorDetails += `
29387
29477
  Request: ${requestDescription}`;
29478
+ }
29479
+ errors.push(errorDetails);
29480
+ await emitFailure(errorDetails, step, stepIdx);
29481
+ return {
29482
+ name: "",
29483
+ success: false,
29484
+ responses,
29485
+ scriptResults,
29486
+ assertionResults,
29487
+ steps: orderedSteps,
29488
+ errors,
29489
+ duration: Date.now() - startTime
29490
+ };
29491
+ }
29388
29492
  }
29389
- errors.push(errorDetails);
29390
- return {
29391
- name: "",
29392
- success: false,
29393
- responses,
29394
- scriptResults,
29395
- assertionResults,
29396
- steps: orderedSteps,
29397
- errors,
29398
- duration: Date.now() - startTime
29399
- };
29493
+ } finally {
29494
+ await emitAfterStep(step, stepIdx);
29400
29495
  }
29401
29496
  }
29497
+ return {
29498
+ name: "",
29499
+ success: errors.filter((e) => !e.startsWith("Warning")).length === 0,
29500
+ responses,
29501
+ scriptResults,
29502
+ assertionResults,
29503
+ steps: orderedSteps,
29504
+ errors,
29505
+ duration: Date.now() - startTime,
29506
+ variables: runtimeVariables
29507
+ };
29508
+ } finally {
29509
+ if (debugHooks?.onSequenceExit) {
29510
+ await debugHooks.onSequenceExit({
29511
+ sequenceName: executionContext?.sequenceName,
29512
+ filePath: executionContext?.filePath,
29513
+ declarationLine: executionContext?.sequenceStartLine,
29514
+ nestingDepth,
29515
+ runtimeVariables,
29516
+ responses
29517
+ });
29518
+ }
29402
29519
  }
29403
- return {
29404
- name: "",
29405
- success: errors.filter((e) => !e.startsWith("Warning")).length === 0,
29406
- responses,
29407
- scriptResults,
29408
- assertionResults,
29409
- steps: orderedSteps,
29410
- errors,
29411
- duration: Date.now() - startTime,
29412
- variables: runtimeVariables
29413
- };
29414
29520
  }
29415
29521
 
29416
29522
  // src/cli/colors.ts
@@ -30591,8 +30697,8 @@ function generateHtmlReportFromResponse(response, testName, options) {
30591
30697
  }
30592
30698
 
30593
30699
  // src/environmentParser.ts
30594
- var fs6 = __toESM(require("fs"));
30595
- var path5 = __toESM(require("path"));
30700
+ var fs7 = __toESM(require("fs"));
30701
+ var path6 = __toESM(require("path"));
30596
30702
 
30597
30703
  // src/secrets/crypto.ts
30598
30704
  var crypto2 = __toESM(require("crypto"));
@@ -30660,30 +30766,48 @@ function decryptSecretValue(encryptedValue, sharedKey) {
30660
30766
  }
30661
30767
 
30662
30768
  // src/secrets/keyStore.ts
30769
+ var fs6 = __toESM(require("fs"));
30770
+ var path5 = __toESM(require("path"));
30771
+
30772
+ // src/cacheDir.ts
30663
30773
  var fs5 = __toESM(require("fs"));
30664
30774
  var path4 = __toESM(require("path"));
30665
- var CACHE_DIR = ".norn-cache";
30775
+ var NORN_CACHE_DIR = ".norn-cache";
30776
+ var CACHE_GITIGNORE_FILE = ".gitignore";
30777
+ var CACHE_GITIGNORE_CONTENT = "*\n";
30778
+ function ensureNornCacheGitignore(cacheDir) {
30779
+ const gitignorePath = path4.join(cacheDir, CACHE_GITIGNORE_FILE);
30780
+ try {
30781
+ const hasDesiredContent = fs5.existsSync(gitignorePath) && fs5.readFileSync(gitignorePath, "utf8") === CACHE_GITIGNORE_CONTENT;
30782
+ if (hasDesiredContent) {
30783
+ return;
30784
+ }
30785
+ fs5.writeFileSync(gitignorePath, CACHE_GITIGNORE_CONTENT, "utf8");
30786
+ } catch {
30787
+ }
30788
+ }
30789
+
30790
+ // src/secrets/keyStore.ts
30666
30791
  var CACHE_FILE = "secret-keys.json";
30667
30792
  var CACHE_VERSION = 1;
30668
- var CACHE_GITIGNORE_RULE = ".norn-cache/";
30669
30793
  var sessionKeys = /* @__PURE__ */ new Map();
30670
30794
  function getSearchStartDirectory(targetPath) {
30671
- const resolvedPath = path4.resolve(targetPath);
30795
+ const resolvedPath = path5.resolve(targetPath);
30672
30796
  try {
30673
- const stats = fs5.statSync(resolvedPath);
30674
- return stats.isDirectory() ? resolvedPath : path4.dirname(resolvedPath);
30797
+ const stats = fs6.statSync(resolvedPath);
30798
+ return stats.isDirectory() ? resolvedPath : path5.dirname(resolvedPath);
30675
30799
  } catch {
30676
- return path4.dirname(resolvedPath);
30800
+ return path5.dirname(resolvedPath);
30677
30801
  }
30678
30802
  }
30679
30803
  function findExistingCacheDir(targetPath) {
30680
30804
  let dir = getSearchStartDirectory(targetPath);
30681
30805
  while (true) {
30682
- const cacheDir = path4.join(dir, CACHE_DIR);
30683
- if (fs5.existsSync(cacheDir) && fs5.statSync(cacheDir).isDirectory()) {
30806
+ const cacheDir = path5.join(dir, NORN_CACHE_DIR);
30807
+ if (fs6.existsSync(cacheDir) && fs6.statSync(cacheDir).isDirectory()) {
30684
30808
  return cacheDir;
30685
30809
  }
30686
- const parent = path4.dirname(dir);
30810
+ const parent = path5.dirname(dir);
30687
30811
  if (parent === dir) {
30688
30812
  return void 0;
30689
30813
  }
@@ -30692,67 +30816,24 @@ function findExistingCacheDir(targetPath) {
30692
30816
  }
30693
30817
  function ensureCacheDir(targetPath) {
30694
30818
  const existing = findExistingCacheDir(targetPath);
30695
- const cacheDir = existing ?? path4.join(getSearchStartDirectory(targetPath), CACHE_DIR);
30696
- if (!fs5.existsSync(cacheDir)) {
30697
- fs5.mkdirSync(cacheDir, { recursive: true });
30819
+ const cacheDir = existing ?? path5.join(getSearchStartDirectory(targetPath), NORN_CACHE_DIR);
30820
+ if (!fs6.existsSync(cacheDir)) {
30821
+ fs6.mkdirSync(cacheDir, { recursive: true });
30698
30822
  }
30823
+ ensureNornCacheGitignore(cacheDir);
30699
30824
  return cacheDir;
30700
30825
  }
30701
30826
  function getCachePath(targetPath) {
30702
30827
  const cacheDir = ensureCacheDir(targetPath);
30703
- return path4.join(cacheDir, CACHE_FILE);
30704
- }
30705
- function findGitRepositoryRoot(targetPath) {
30706
- let dir = getSearchStartDirectory(targetPath);
30707
- while (true) {
30708
- const gitPath = path4.join(dir, ".git");
30709
- if (fs5.existsSync(gitPath)) {
30710
- return dir;
30711
- }
30712
- const parent = path4.dirname(dir);
30713
- if (parent === dir) {
30714
- return void 0;
30715
- }
30716
- dir = parent;
30717
- }
30718
- }
30719
- function lineIgnoresNornCache(line2) {
30720
- const trimmed = line2.trim();
30721
- if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) {
30722
- return false;
30723
- }
30724
- return /(^|\/)\.norn-cache\/?$/.test(trimmed);
30725
- }
30726
- function ensureNornCacheGitIgnored(targetPath) {
30727
- const repoRoot = findGitRepositoryRoot(targetPath);
30728
- if (!repoRoot) {
30729
- return;
30730
- }
30731
- const gitignorePath = path4.join(repoRoot, ".gitignore");
30732
- let current = "";
30733
- try {
30734
- if (fs5.existsSync(gitignorePath)) {
30735
- current = fs5.readFileSync(gitignorePath, "utf8");
30736
- const lines = current.split(/\r?\n/);
30737
- if (lines.some(lineIgnoresNornCache)) {
30738
- return;
30739
- }
30740
- }
30741
- const prefix = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
30742
- const addition = `${prefix}# Norn cache
30743
- ${CACHE_GITIGNORE_RULE}
30744
- `;
30745
- fs5.writeFileSync(gitignorePath, current + addition, "utf8");
30746
- } catch {
30747
- }
30828
+ return path5.join(cacheDir, CACHE_FILE);
30748
30829
  }
30749
30830
  function readCache(targetPath) {
30750
30831
  const cachePath = getCachePath(targetPath);
30751
- if (!fs5.existsSync(cachePath)) {
30832
+ if (!fs6.existsSync(cachePath)) {
30752
30833
  return { version: CACHE_VERSION, keys: {} };
30753
30834
  }
30754
30835
  try {
30755
- const parsed = JSON.parse(fs5.readFileSync(cachePath, "utf8"));
30836
+ const parsed = JSON.parse(fs6.readFileSync(cachePath, "utf8"));
30756
30837
  if (parsed.version !== CACHE_VERSION || typeof parsed.keys !== "object" || parsed.keys === null) {
30757
30838
  return { version: CACHE_VERSION, keys: {} };
30758
30839
  }
@@ -30763,9 +30844,9 @@ function readCache(targetPath) {
30763
30844
  }
30764
30845
  function writeCache(targetPath, cache) {
30765
30846
  const cachePath = getCachePath(targetPath);
30766
- fs5.writeFileSync(cachePath, JSON.stringify(cache, null, 2), { encoding: "utf8", mode: 384 });
30847
+ fs6.writeFileSync(cachePath, JSON.stringify(cache, null, 2), { encoding: "utf8", mode: 384 });
30767
30848
  try {
30768
- fs5.chmodSync(cachePath, 384);
30849
+ fs6.chmodSync(cachePath, 384);
30769
30850
  } catch {
30770
30851
  }
30771
30852
  }
@@ -30818,7 +30899,6 @@ function setSessionSecretKey(kid, key) {
30818
30899
  sessionKeys.set(kid, key);
30819
30900
  }
30820
30901
  function saveSecretKeyToCache(kid, key, targetPath) {
30821
- ensureNornCacheGitIgnored(targetPath);
30822
30902
  const cache = readCache(targetPath);
30823
30903
  cache.keys[kid] = {
30824
30904
  value: key,
@@ -30922,22 +31002,22 @@ function parseEnvFile(content, sourceFilePath) {
30922
31002
  return config;
30923
31003
  }
30924
31004
  function getEnvSearchStartDirectory(targetPath) {
30925
- const resolvedPath = path5.resolve(targetPath);
31005
+ const resolvedPath = path6.resolve(targetPath);
30926
31006
  try {
30927
- const stats = fs6.statSync(resolvedPath);
30928
- return stats.isDirectory() ? resolvedPath : path5.dirname(resolvedPath);
31007
+ const stats = fs7.statSync(resolvedPath);
31008
+ return stats.isDirectory() ? resolvedPath : path6.dirname(resolvedPath);
30929
31009
  } catch {
30930
- return path5.dirname(resolvedPath);
31010
+ return path6.dirname(resolvedPath);
30931
31011
  }
30932
31012
  }
30933
31013
  function findEnvFileFromPath(filePath) {
30934
31014
  let dir = getEnvSearchStartDirectory(filePath);
30935
31015
  while (true) {
30936
- const envPath = path5.join(dir, ENV_FILENAME);
30937
- if (fs6.existsSync(envPath)) {
31016
+ const envPath = path6.join(dir, ENV_FILENAME);
31017
+ if (fs7.existsSync(envPath)) {
30938
31018
  return envPath;
30939
31019
  }
30940
- const parentDir = path5.dirname(dir);
31020
+ const parentDir = path6.dirname(dir);
30941
31021
  if (parentDir === dir) {
30942
31022
  break;
30943
31023
  }
@@ -30962,7 +31042,7 @@ function resolveNornenvImports(config, baseDir, entryFilePath, readFile2, import
30962
31042
  }
30963
31043
  for (const imp of config.imports) {
30964
31044
  const resolvedImportPath = resolveImportPath(imp.path, baseDir);
30965
- if (!resolvedImportPath || !fs6.existsSync(resolvedImportPath)) {
31045
+ if (!resolvedImportPath || !fs7.existsSync(resolvedImportPath)) {
30966
31046
  errors.push({
30967
31047
  message: `Imported file not found: '${imp.path}'`,
30968
31048
  filePath: entryFilePath,
@@ -30978,10 +31058,10 @@ function resolveNornenvImports(config, baseDir, entryFilePath, readFile2, import
30978
31058
  });
30979
31059
  continue;
30980
31060
  }
30981
- const normalizedPath = path5.resolve(resolvedImportPath);
31061
+ const normalizedPath = path6.resolve(resolvedImportPath);
30982
31062
  if (stack.includes(normalizedPath)) {
30983
- const entryDir = path5.dirname(stack[0]);
30984
- const cycle = [...stack, normalizedPath].map((p) => path5.relative(entryDir, p) || path5.basename(p)).join(" \u2192 ");
31063
+ const entryDir = path6.dirname(stack[0]);
31064
+ const cycle = [...stack, normalizedPath].map((p) => path6.relative(entryDir, p) || path6.basename(p)).join(" \u2192 ");
30985
31065
  errors.push({
30986
31066
  message: `Circular import detected: ${cycle}`,
30987
31067
  filePath: entryFilePath,
@@ -31010,7 +31090,7 @@ function resolveNornenvImports(config, baseDir, entryFilePath, readFile2, import
31010
31090
  if (importedConfig.imports.length > 0) {
31011
31091
  const childResult = resolveNornenvImports(
31012
31092
  importedConfig,
31013
- path5.dirname(normalizedPath),
31093
+ path6.dirname(normalizedPath),
31014
31094
  normalizedPath,
31015
31095
  readFile2,
31016
31096
  [...stack, normalizedPath],
@@ -31028,7 +31108,7 @@ function resolveNornenvImports(config, baseDir, entryFilePath, readFile2, import
31028
31108
  }
31029
31109
  function resolveImportPath(importPath, baseDir) {
31030
31110
  const cleaned = importPath.replace(/^["']|["']$/g, "");
31031
- return path5.resolve(baseDir, cleaned);
31111
+ return path6.resolve(baseDir, cleaned);
31032
31112
  }
31033
31113
  function registerVariableOrigins(config, filePath, origins) {
31034
31114
  for (const varName of Object.keys(config.common)) {
@@ -31092,12 +31172,12 @@ function mergeConfigs(target, source, targetFilePath, sourceFilePath, variableOr
31092
31172
  }
31093
31173
  }
31094
31174
  function toDisplayPath(filePath, entryFilePath) {
31095
- const entryDir = path5.dirname(entryFilePath);
31096
- const relative4 = path5.relative(entryDir, filePath);
31097
- return relative4 && relative4 !== "" ? relative4 : path5.basename(filePath);
31175
+ const entryDir = path6.dirname(entryFilePath);
31176
+ const relative4 = path6.relative(entryDir, filePath);
31177
+ return relative4 && relative4 !== "" ? relative4 : path6.basename(filePath);
31098
31178
  }
31099
31179
  function loadAndResolveEnvFile(filePath) {
31100
- const content = fs6.readFileSync(filePath, "utf-8");
31180
+ const content = fs7.readFileSync(filePath, "utf-8");
31101
31181
  const config = parseEnvFile(content, filePath);
31102
31182
  if (config.imports.length === 0) {
31103
31183
  const secretErrors = resolveEncryptedSecretValues(config, filePath);
@@ -31105,9 +31185,9 @@ function loadAndResolveEnvFile(filePath) {
31105
31185
  }
31106
31186
  const result = resolveNornenvImports(
31107
31187
  config,
31108
- path5.dirname(filePath),
31188
+ path6.dirname(filePath),
31109
31189
  filePath,
31110
- (p) => fs6.readFileSync(p, "utf-8")
31190
+ (p) => fs7.readFileSync(p, "utf-8")
31111
31191
  );
31112
31192
  result.secretErrors.push(...resolveEncryptedSecretValues(result.config, filePath));
31113
31193
  return result;
@@ -31173,14 +31253,14 @@ function resolveEncryptedSecretValues(config, entryFilePath) {
31173
31253
  }
31174
31254
 
31175
31255
  // src/secrets/cliSecrets.ts
31176
- var fs8 = __toESM(require("fs"));
31177
- var path7 = __toESM(require("path"));
31256
+ var fs9 = __toESM(require("fs"));
31257
+ var path8 = __toESM(require("path"));
31178
31258
  var readline = __toESM(require("readline"));
31179
31259
  var import_process = require("process");
31180
31260
 
31181
31261
  // src/secrets/envFileSecrets.ts
31182
- var fs7 = __toESM(require("fs"));
31183
- var path6 = __toESM(require("path"));
31262
+ var fs8 = __toESM(require("fs"));
31263
+ var path7 = __toESM(require("path"));
31184
31264
  var envRegex2 = /^\s*\[env:([a-zA-Z_][a-zA-Z0-9_-]*)\]\s*$/;
31185
31265
  var secretRegex2 = /^(\s*secret\s+)([a-zA-Z_][a-zA-Z0-9_]*)(\s*=\s*)(.+)$/;
31186
31266
  function extractSecretLines(content, filePath) {
@@ -31248,7 +31328,7 @@ function findSecretLine(content, variableName, envName) {
31248
31328
  return secretLines.find((secret) => secret.name === variableName);
31249
31329
  }
31250
31330
  function loadSecretLine(filePath, variableName, envName) {
31251
- const content = fs7.readFileSync(filePath, "utf8");
31331
+ const content = fs8.readFileSync(filePath, "utf8");
31252
31332
  const secret = findSecretLine(content, variableName, envName);
31253
31333
  if (!secret) {
31254
31334
  const envLabel = envName ? ` in [env:${envName}]` : "";
@@ -31257,22 +31337,22 @@ function loadSecretLine(filePath, variableName, envName) {
31257
31337
  return { content, secret };
31258
31338
  }
31259
31339
  function writeSecretLine(filePath, content) {
31260
- fs7.writeFileSync(filePath, content, "utf8");
31340
+ fs8.writeFileSync(filePath, content, "utf8");
31261
31341
  }
31262
31342
  function discoverNornenvFiles(targetPath) {
31263
- const resolved = path6.resolve(targetPath);
31264
- if (!fs7.existsSync(resolved)) {
31343
+ const resolved = path7.resolve(targetPath);
31344
+ if (!fs8.existsSync(resolved)) {
31265
31345
  return [];
31266
31346
  }
31267
- const stats = fs7.statSync(resolved);
31347
+ const stats = fs8.statSync(resolved);
31268
31348
  if (stats.isFile()) {
31269
- return path6.basename(resolved) === ".nornenv" ? [resolved] : [];
31349
+ return path7.basename(resolved) === ".nornenv" ? [resolved] : [];
31270
31350
  }
31271
31351
  const results = [];
31272
31352
  const walk = (dir) => {
31273
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
31353
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
31274
31354
  for (const entry of entries) {
31275
- const fullPath = path6.join(dir, entry.name);
31355
+ const fullPath = path7.join(dir, entry.name);
31276
31356
  if (entry.isDirectory()) {
31277
31357
  if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist" || entry.name === "out") {
31278
31358
  continue;
@@ -31331,8 +31411,8 @@ function formatSecretError(err, envFilePath) {
31331
31411
  if (!envFilePath) {
31332
31412
  return `${err.message}`;
31333
31413
  }
31334
- const relative4 = path7.relative(path7.dirname(envFilePath), err.filePath);
31335
- const fileLabel = relative4 && relative4 !== "" ? relative4 : path7.basename(err.filePath);
31414
+ const relative4 = path8.relative(path8.dirname(envFilePath), err.filePath);
31415
+ const fileLabel = relative4 && relative4 !== "" ? relative4 : path8.basename(err.filePath);
31336
31416
  const lineLabel = err.line >= 0 ? `${fileLabel}:${err.line + 1}` : fileLabel;
31337
31417
  return `${lineLabel} - ${err.message}`;
31338
31418
  }
@@ -31428,7 +31508,7 @@ async function handleEncrypt(args) {
31428
31508
  console.error(`Error: encrypt requires --file and --var.`);
31429
31509
  return 1;
31430
31510
  }
31431
- const absoluteFilePath = path7.resolve(filePath);
31511
+ const absoluteFilePath = path8.resolve(filePath);
31432
31512
  const { content, secret } = loadSecretLine(absoluteFilePath, variableName, envName);
31433
31513
  if (secret.encrypted) {
31434
31514
  console.error(`Error: Secret '${variableName}' is already encrypted. Use rotate instead.`);
@@ -31460,7 +31540,7 @@ async function handleRotate(args) {
31460
31540
  console.error(`Error: rotate requires --file and --var.`);
31461
31541
  return 1;
31462
31542
  }
31463
- const absoluteFilePath = path7.resolve(filePath);
31543
+ const absoluteFilePath = path8.resolve(filePath);
31464
31544
  const { content, secret } = loadSecretLine(absoluteFilePath, variableName, envName);
31465
31545
  let kid = explicitKid;
31466
31546
  if (!kid && secret.encrypted) {
@@ -31491,7 +31571,7 @@ async function handleRotate(args) {
31491
31571
  }
31492
31572
  async function handleRekey(args) {
31493
31573
  const positional = args.filter((arg) => !arg.startsWith("-"));
31494
- const targetPath = positional[0] ? path7.resolve(positional[0]) : process.cwd();
31574
+ const targetPath = positional[0] ? path8.resolve(positional[0]) : process.cwd();
31495
31575
  const targetKid = getFlagValue(args, "--kid");
31496
31576
  const files = discoverNornenvFiles(targetPath);
31497
31577
  if (files.length === 0) {
@@ -31501,7 +31581,7 @@ async function handleRekey(args) {
31501
31581
  let updatedFiles = 0;
31502
31582
  let updatedSecrets = 0;
31503
31583
  for (const filePath of files) {
31504
- const original = fs8.readFileSync(filePath, "utf8");
31584
+ const original = fs9.readFileSync(filePath, "utf8");
31505
31585
  const secretLines = extractSecretLines(original, filePath);
31506
31586
  if (secretLines.length === 0) {
31507
31587
  continue;
@@ -31552,7 +31632,7 @@ async function handleRekey(args) {
31552
31632
  updatedSecrets += 1;
31553
31633
  }
31554
31634
  if (updated !== original) {
31555
- fs8.writeFileSync(filePath, updated, "utf8");
31635
+ fs9.writeFileSync(filePath, updated, "utf8");
31556
31636
  updatedFiles += 1;
31557
31637
  }
31558
31638
  }
@@ -31561,7 +31641,7 @@ async function handleRekey(args) {
31561
31641
  }
31562
31642
  async function handleAudit(args) {
31563
31643
  const positional = args.filter((arg) => !arg.startsWith("-"));
31564
- const targetPath = positional[0] ? path7.resolve(positional[0]) : process.cwd();
31644
+ const targetPath = positional[0] ? path8.resolve(positional[0]) : process.cwd();
31565
31645
  const files = discoverNornenvFiles(targetPath);
31566
31646
  if (files.length === 0) {
31567
31647
  console.log(`No .nornenv files found under ${targetPath}`);
@@ -31569,7 +31649,7 @@ async function handleAudit(args) {
31569
31649
  }
31570
31650
  const issues = [];
31571
31651
  for (const filePath of files) {
31572
- const content = fs8.readFileSync(filePath, "utf8");
31652
+ const content = fs9.readFileSync(filePath, "utf8");
31573
31653
  const secrets = extractSecretLines(content, filePath);
31574
31654
  for (const secret of secrets) {
31575
31655
  if (!secret.encrypted) {
@@ -31656,9 +31736,9 @@ function printSecretResolutionErrors(errors, envFilePath) {
31656
31736
 
31657
31737
  // src/cli.ts
31658
31738
  function formatNornenvErrorLocation(rootEnvFilePath, errorFilePath, line2) {
31659
- const baseDir = path8.dirname(rootEnvFilePath);
31660
- const relativePath = path8.relative(baseDir, errorFilePath);
31661
- const fileLabel = relativePath && relativePath !== "" ? relativePath : path8.basename(errorFilePath);
31739
+ const baseDir = path9.dirname(rootEnvFilePath);
31740
+ const relativePath = path9.relative(baseDir, errorFilePath);
31741
+ const fileLabel = relativePath && relativePath !== "" ? relativePath : path9.basename(errorFilePath);
31662
31742
  return line2 >= 0 ? `${fileLabel}:${line2 + 1}` : fileLabel;
31663
31743
  }
31664
31744
  function resolveEnvironmentForPath(targetPath, selectedEnv) {
@@ -31740,10 +31820,10 @@ function generateTimestamp() {
31740
31820
  return `${year}-${month}-${day}-${hours}${minutes}${seconds}`;
31741
31821
  }
31742
31822
  function generateReportPaths(outputDir, inputFile, timestamp) {
31743
- const baseName = path8.basename(inputFile, path8.extname(inputFile));
31823
+ const baseName = path9.basename(inputFile, path9.extname(inputFile));
31744
31824
  return {
31745
- junitPath: path8.join(outputDir, `${baseName}-${timestamp}-results.xml`),
31746
- htmlPath: path8.join(outputDir, `${baseName}-${timestamp}-report.html`)
31825
+ junitPath: path9.join(outputDir, `${baseName}-${timestamp}-results.xml`),
31826
+ htmlPath: path9.join(outputDir, `${baseName}-${timestamp}-report.html`)
31747
31827
  };
31748
31828
  }
31749
31829
  function parseArgs(args) {
@@ -32011,9 +32091,9 @@ async function runSingleRequest(fileContent, variables, cookieJar, apiDefinition
32011
32091
  function discoverNornFiles(dirPath) {
32012
32092
  const files = [];
32013
32093
  function walkDir(currentPath) {
32014
- const entries = fs9.readdirSync(currentPath, { withFileTypes: true });
32094
+ const entries = fs10.readdirSync(currentPath, { withFileTypes: true });
32015
32095
  for (const entry of entries) {
32016
- const fullPath = path8.join(currentPath, entry.name);
32096
+ const fullPath = path9.join(currentPath, entry.name);
32017
32097
  if (entry.isDirectory()) {
32018
32098
  if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
32019
32099
  walkDir(fullPath);
@@ -32033,7 +32113,7 @@ function countTestSequences(fileContent, tagFilterOptions) {
32033
32113
  return { total: testSequences.length, filtered };
32034
32114
  }
32035
32115
  async function loadTheoryFile(theoryPath, workingDir) {
32036
- const absolutePath = path8.resolve(workingDir, theoryPath);
32116
+ const absolutePath = path9.resolve(workingDir, theoryPath);
32037
32117
  const content = await fsPromises.readFile(absolutePath, "utf-8");
32038
32118
  return JSON.parse(content);
32039
32119
  }
@@ -32152,12 +32232,12 @@ async function main() {
32152
32232
  if (options.insecure) {
32153
32233
  console.error(colors.warning("Warning: TLS certificate verification is disabled (--insecure). Use this only for local development."));
32154
32234
  }
32155
- const inputPath = path8.resolve(options.file);
32156
- if (!fs9.existsSync(inputPath)) {
32235
+ const inputPath = path9.resolve(options.file);
32236
+ if (!fs10.existsSync(inputPath)) {
32157
32237
  console.error(`Error: Path not found: ${inputPath}`);
32158
32238
  process.exit(1);
32159
32239
  }
32160
- const isDirectory = fs9.statSync(inputPath).isDirectory();
32240
+ const isDirectory = fs10.statSync(inputPath).isDirectory();
32161
32241
  let filesToRun;
32162
32242
  if (isDirectory) {
32163
32243
  filesToRun = discoverNornFiles(inputPath);
@@ -32224,11 +32304,11 @@ async function main() {
32224
32304
  }
32225
32305
  mergeSecrets(combinedSecretNames, combinedSecretValues, resolvedEnv.secretNames, resolvedEnv.secretValues);
32226
32306
  const redaction2 = createRedactionOptions(combinedSecretNames, combinedSecretValues, !options.noRedact);
32227
- const fileContent = fs9.readFileSync(filePath, "utf-8");
32307
+ const fileContent = fs10.readFileSync(filePath, "utf-8");
32228
32308
  const fileVariables = extractFileLevelVariables(fileContent);
32229
32309
  const variables = { ...resolvedEnv.variables, ...fileVariables };
32230
32310
  const cookieJar = createCookieJar();
32231
- const workingDir = path8.dirname(filePath);
32311
+ const workingDir = path9.dirname(filePath);
32232
32312
  const importResult = await resolveImports(
32233
32313
  fileContent,
32234
32314
  workingDir,
@@ -32329,7 +32409,7 @@ ${fileContent}` : fileContent;
32329
32409
  let totalTestCount = 0;
32330
32410
  let filteredTestCount = 0;
32331
32411
  for (const filePath of filesToRun) {
32332
- const fileContent = fs9.readFileSync(filePath, "utf-8");
32412
+ const fileContent = fs10.readFileSync(filePath, "utf-8");
32333
32413
  const counts = countTestSequences(fileContent, tagFilterOptions);
32334
32414
  totalTestCount += counts.total;
32335
32415
  filteredTestCount += counts.filtered;
@@ -32371,16 +32451,16 @@ ${fileContent}` : fileContent;
32371
32451
  process.exit(1);
32372
32452
  }
32373
32453
  if (!resolvedEnv.envFilePath && options.env) {
32374
- const relPath = isDirectory ? path8.relative(inputPath, filePath) : path8.basename(filePath);
32454
+ const relPath = isDirectory ? path9.relative(inputPath, filePath) : path9.basename(filePath);
32375
32455
  console.error(colors.warning(`Warning: --env specified but no .nornenv file found for ${relPath}`));
32376
32456
  }
32377
32457
  mergeSecrets(combinedSecretNames, combinedSecretValues, resolvedEnv.secretNames, resolvedEnv.secretValues);
32378
32458
  const redaction2 = createRedactionOptions(combinedSecretNames, combinedSecretValues, !options.noRedact);
32379
- const fileContent = fs9.readFileSync(filePath, "utf-8");
32459
+ const fileContent = fs10.readFileSync(filePath, "utf-8");
32380
32460
  const fileVariables = extractFileLevelVariables(fileContent);
32381
32461
  const variables = { ...resolvedEnv.variables, ...fileVariables };
32382
32462
  const cookieJar = createCookieJar();
32383
- const workingDir = path8.dirname(filePath);
32463
+ const workingDir = path9.dirname(filePath);
32384
32464
  const importResult = await resolveImports(
32385
32465
  fileContent,
32386
32466
  workingDir,
@@ -32412,7 +32492,7 @@ ${fileContent}` : fileContent;
32412
32492
  continue;
32413
32493
  }
32414
32494
  if (isDirectory && options.output !== "json") {
32415
- const relPath = path8.relative(inputPath, filePath);
32495
+ const relPath = path9.relative(inputPath, filePath);
32416
32496
  console.log(colors.info(`
32417
32497
  \u2501\u2501\u2501 ${relPath} \u2501\u2501\u2501`));
32418
32498
  }
@@ -32456,7 +32536,7 @@ ${fileContent}` : fileContent;
32456
32536
  let htmlOutputPath = options.htmlOutput;
32457
32537
  if (options.outputDir) {
32458
32538
  const timestamp = generateTimestamp();
32459
- const baseName = isDirectory ? path8.basename(inputPath) : path8.basename(inputPath, path8.extname(inputPath));
32539
+ const baseName = isDirectory ? path9.basename(inputPath) : path9.basename(inputPath, path9.extname(inputPath));
32460
32540
  const generatedPaths = generateReportPaths(options.outputDir, baseName + ".norn", timestamp);
32461
32541
  if (!junitOutputPath) {
32462
32542
  junitOutputPath = generatedPaths.junitPath;
@@ -32464,12 +32544,12 @@ ${fileContent}` : fileContent;
32464
32544
  if (!htmlOutputPath) {
32465
32545
  htmlOutputPath = generatedPaths.htmlPath;
32466
32546
  }
32467
- if (!fs9.existsSync(options.outputDir)) {
32468
- fs9.mkdirSync(options.outputDir, { recursive: true });
32547
+ if (!fs10.existsSync(options.outputDir)) {
32548
+ fs10.mkdirSync(options.outputDir, { recursive: true });
32469
32549
  }
32470
32550
  }
32471
32551
  if (junitOutputPath) {
32472
- const suiteName = isDirectory ? path8.basename(inputPath) : path8.basename(inputPath, path8.extname(inputPath));
32552
+ const suiteName = isDirectory ? path9.basename(inputPath) : path9.basename(inputPath, path9.extname(inputPath));
32473
32553
  if (result.type === "request") {
32474
32554
  generateJUnitReportFromResponse(
32475
32555
  result.results[0],
@@ -32485,11 +32565,11 @@ ${fileContent}` : fileContent;
32485
32565
  console.log(colors.info(`JUnit report written to: ${junitOutputPath}`));
32486
32566
  }
32487
32567
  if (htmlOutputPath) {
32488
- const title = `Norn Test Report - ${path8.basename(inputPath)}`;
32568
+ const title = `Norn Test Report - ${path9.basename(inputPath)}`;
32489
32569
  if (result.type === "request") {
32490
32570
  generateHtmlReportFromResponse(
32491
32571
  result.results[0],
32492
- options.request || path8.basename(inputPath),
32572
+ options.request || path9.basename(inputPath),
32493
32573
  { outputPath: htmlOutputPath, redaction, title }
32494
32574
  );
32495
32575
  } else {