norn-cli 2.4.0 → 2.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.
Files changed (96) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +26 -1
  3. package/dist/cli.js +330 -85
  4. package/package.json +24 -5
  5. package/schemas/norn.config.schema.json +43 -1
  6. package/scripts/__pycache__/reddit_signal_miner.cpython-312.pyc +0 -0
  7. package/scripts/reddit_signal_miner.py +482 -0
  8. package/.claude/settings.local.json +0 -18
  9. package/.claude/skills/norn-social-campaign/SKILL.md +0 -70
  10. package/out/apiResponseIntellisenseCache.js +0 -394
  11. package/out/assertionRunner.js +0 -567
  12. package/out/cacheDir.js +0 -136
  13. package/out/chatParticipant.js +0 -763
  14. package/out/cli/colors.js +0 -127
  15. package/out/cli/formatters/assertion.js +0 -102
  16. package/out/cli/formatters/index.js +0 -23
  17. package/out/cli/formatters/response.js +0 -106
  18. package/out/cli/formatters/summary.js +0 -246
  19. package/out/cli/redaction.js +0 -237
  20. package/out/cli/reporters/html.js +0 -689
  21. package/out/cli/reporters/index.js +0 -22
  22. package/out/cli/reporters/junit.js +0 -226
  23. package/out/codeLensProvider.js +0 -351
  24. package/out/compareContentProvider.js +0 -85
  25. package/out/completionProvider.js +0 -3739
  26. package/out/contractAssertionSummary.js +0 -225
  27. package/out/contractDecorationProvider.js +0 -243
  28. package/out/coverageCalculator.js +0 -879
  29. package/out/coveragePanel.js +0 -597
  30. package/out/debug/breakpointResolver.js +0 -84
  31. package/out/debug/breakpoints.js +0 -52
  32. package/out/debug/nornDebugAdapter.js +0 -166
  33. package/out/debug/nornDebugSession.js +0 -613
  34. package/out/debug/sequenceLocationIndex.js +0 -77
  35. package/out/debug/types.js +0 -3
  36. package/out/deepClone.js +0 -21
  37. package/out/diagnosticProvider.js +0 -2554
  38. package/out/environmentParser.js +0 -736
  39. package/out/environmentProvider.js +0 -544
  40. package/out/environmentTemplates.js +0 -146
  41. package/out/errors/formatError.js +0 -113
  42. package/out/errors/nornError.js +0 -29
  43. package/out/formUrlEncoded.js +0 -89
  44. package/out/httpClient.js +0 -348
  45. package/out/httpRuntimeOptions.js +0 -16
  46. package/out/importErrors.js +0 -31
  47. package/out/inlayHintResolver.js +0 -70
  48. package/out/jsonFileReader.js +0 -323
  49. package/out/mcpClient.js +0 -193
  50. package/out/mcpConfig.js +0 -184
  51. package/out/mcpToolIntellisenseCache.js +0 -96
  52. package/out/mcpToolSchema.js +0 -50
  53. package/out/nornConfig.js +0 -132
  54. package/out/nornHoverProvider.js +0 -124
  55. package/out/nornInlayHintsProvider.js +0 -191
  56. package/out/nornPrompt.js +0 -755
  57. package/out/nornSqlParser.js +0 -286
  58. package/out/nornapiHoverProvider.js +0 -135
  59. package/out/nornapiInlayHintsProvider.js +0 -94
  60. package/out/nornapiParser.js +0 -324
  61. package/out/nornenvCodeActionProvider.js +0 -101
  62. package/out/nornenvDecorationProvider.js +0 -239
  63. package/out/nornenvFoldingProvider.js +0 -63
  64. package/out/nornenvHoverProvider.js +0 -114
  65. package/out/nornenvInlayHintsProvider.js +0 -99
  66. package/out/nornenvLanguageModel.js +0 -187
  67. package/out/nornenvRegionRefactor.js +0 -267
  68. package/out/nornsqlHoverProvider.js +0 -95
  69. package/out/nornsqlInlayHintsProvider.js +0 -114
  70. package/out/parser.js +0 -839
  71. package/out/pathAccess.js +0 -28
  72. package/out/postmanImportPanel.js +0 -732
  73. package/out/postmanImportPlanner.js +0 -1155
  74. package/out/postmanImportSidebarView.js +0 -532
  75. package/out/quotedString.js +0 -35
  76. package/out/requestPreparation.js +0 -179
  77. package/out/requestValidation.js +0 -146
  78. package/out/responsePanel.js +0 -7754
  79. package/out/schemaGenerator.js +0 -562
  80. package/out/scriptRunner.js +0 -419
  81. package/out/secrets/cliSecrets.js +0 -415
  82. package/out/secrets/crypto.js +0 -105
  83. package/out/secrets/envFileSecrets.js +0 -177
  84. package/out/secrets/keyStore.js +0 -259
  85. package/out/sequenceDeclaration.js +0 -15
  86. package/out/sequenceRunner.js +0 -3590
  87. package/out/sqlAdapterRunner.js +0 -122
  88. package/out/sqlBuiltInAdapters.js +0 -604
  89. package/out/sqlConfig.js +0 -184
  90. package/out/starterCatalog.js +0 -554
  91. package/out/stringUtils.js +0 -25
  92. package/out/swaggerBodyIntellisenseCache.js +0 -114
  93. package/out/swaggerParser.js +0 -464
  94. package/out/testProvider.js +0 -767
  95. package/out/theoryCaseLoader.js +0 -113
  96. package/out/validationCache.js +0 -211
package/dist/cli.js CHANGED
@@ -101832,6 +101832,123 @@ var path19 = __toESM(require("path"));
101832
101832
  // src/parser.ts
101833
101833
  var path = __toESM(require("path"));
101834
101834
 
101835
+ // src/quotedString.ts
101836
+ function decodeQuotedStringLiteral(literal2) {
101837
+ if (isVerbatimStringLiteral(literal2)) {
101838
+ return decodeVerbatimStringLiteral(literal2);
101839
+ }
101840
+ if (!isQuotedStringLiteral(literal2)) {
101841
+ return literal2;
101842
+ }
101843
+ const quoteChar = literal2[0];
101844
+ const inner = literal2.slice(1, -1);
101845
+ let decoded = "";
101846
+ for (let i = 0; i < inner.length; i++) {
101847
+ const char = inner[i];
101848
+ if (char !== "\\" || i === inner.length - 1) {
101849
+ decoded += char;
101850
+ continue;
101851
+ }
101852
+ const nextChar = inner[i + 1];
101853
+ if (nextChar === "\\" || nextChar === quoteChar) {
101854
+ decoded += nextChar;
101855
+ i++;
101856
+ continue;
101857
+ }
101858
+ decoded += char;
101859
+ }
101860
+ return decoded;
101861
+ }
101862
+ function isQuotedStringLiteral(literal2) {
101863
+ if (isVerbatimStringLiteral(literal2)) {
101864
+ return true;
101865
+ }
101866
+ if (literal2.length < 2) {
101867
+ return false;
101868
+ }
101869
+ const quoteChar = literal2[0];
101870
+ return (quoteChar === '"' || quoteChar === "'") && literal2[literal2.length - 1] === quoteChar;
101871
+ }
101872
+ function isVerbatimStringLiteral(literal2) {
101873
+ return literal2.length >= 3 && literal2.startsWith('@"') && literal2.endsWith('"');
101874
+ }
101875
+ function mapOutsideVerbatimStrings(text, transform2) {
101876
+ let output2 = "";
101877
+ let segmentStart = 0;
101878
+ let inStandardQuote = false;
101879
+ let standardQuoteChar = "";
101880
+ let escapeNext = false;
101881
+ for (let i = 0; i < text.length; i++) {
101882
+ const char = text[i];
101883
+ const nextChar = i + 1 < text.length ? text[i + 1] : "";
101884
+ if (inStandardQuote) {
101885
+ if (escapeNext) {
101886
+ escapeNext = false;
101887
+ continue;
101888
+ }
101889
+ if (char === "\\") {
101890
+ escapeNext = true;
101891
+ continue;
101892
+ }
101893
+ if (char === standardQuoteChar) {
101894
+ inStandardQuote = false;
101895
+ standardQuoteChar = "";
101896
+ }
101897
+ continue;
101898
+ }
101899
+ if (char === "@" && nextChar === '"') {
101900
+ const end = findVerbatimStringEnd(text, i);
101901
+ output2 += transform2(text.substring(segmentStart, i));
101902
+ output2 += text.substring(i, end);
101903
+ segmentStart = end;
101904
+ i = end - 1;
101905
+ continue;
101906
+ }
101907
+ if (char === '"' || char === "'") {
101908
+ inStandardQuote = true;
101909
+ standardQuoteChar = char;
101910
+ escapeNext = false;
101911
+ }
101912
+ }
101913
+ output2 += transform2(text.substring(segmentStart));
101914
+ return output2;
101915
+ }
101916
+ function findVerbatimStringEnd(text, start) {
101917
+ let i = start + 2;
101918
+ while (i < text.length) {
101919
+ const char = text[i];
101920
+ const nextChar = i + 1 < text.length ? text[i + 1] : "";
101921
+ if (char === '"' && nextChar === '"') {
101922
+ i += 2;
101923
+ continue;
101924
+ }
101925
+ if (char === '"' && isLikelyVerbatimStringTerminator(text, i)) {
101926
+ return i + 1;
101927
+ }
101928
+ i++;
101929
+ }
101930
+ return text.length;
101931
+ }
101932
+ function isLikelyVerbatimStringTerminator(text, quoteIndex) {
101933
+ const remainder = text.substring(quoteIndex + 1).trimStart();
101934
+ return remainder === "" || remainder.startsWith("|") || remainder.startsWith("#") || remainder.startsWith(",") || remainder.startsWith(")") || remainder.startsWith("]") || remainder.startsWith("}");
101935
+ }
101936
+ function decodeVerbatimStringLiteral(literal2) {
101937
+ const inner = literal2.slice(2, -1);
101938
+ let decoded = "";
101939
+ for (let i = 0; i < inner.length; i++) {
101940
+ const char = inner[i];
101941
+ const nextChar = i + 1 < inner.length ? inner[i + 1] : "";
101942
+ if (char === '"' && nextChar === '"') {
101943
+ decoded += '"';
101944
+ i++;
101945
+ continue;
101946
+ }
101947
+ decoded += char;
101948
+ }
101949
+ return decoded;
101950
+ }
101951
+
101835
101952
  // src/nornapiParser.ts
101836
101953
  function extractPathParameters(path20) {
101837
101954
  const params = [];
@@ -102003,8 +102120,8 @@ function parseApiRequest(requestContent, endpoints, headerGroups) {
102003
102120
  const headerMatch = line2.match(/^([a-zA-Z][a-zA-Z0-9\-]*)\s*:\s*(.+)$/);
102004
102121
  if (headerMatch) {
102005
102122
  let headerValue = headerMatch[2].trim();
102006
- if (headerValue.startsWith('"') && headerValue.endsWith('"') || headerValue.startsWith("'") && headerValue.endsWith("'")) {
102007
- headerValue = headerValue.slice(1, -1);
102123
+ if (isQuotedStringLiteral(headerValue)) {
102124
+ headerValue = decodeQuotedStringLiteral(headerValue);
102008
102125
  }
102009
102126
  inlineHeaders[headerMatch[1]] = headerValue;
102010
102127
  continue;
@@ -102049,8 +102166,8 @@ function parseParamTokens(paramsStr) {
102049
102166
  return tokens;
102050
102167
  }
102051
102168
  function unquote(value) {
102052
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
102053
- return value.slice(1, -1);
102169
+ if (isQuotedStringLiteral(value)) {
102170
+ return decodeQuotedStringLiteral(value);
102054
102171
  }
102055
102172
  return value;
102056
102173
  }
@@ -102367,14 +102484,21 @@ function getNestedPathValue(obj, path20) {
102367
102484
  function stripInlineComment(line2) {
102368
102485
  let inSingleQuote = false;
102369
102486
  let inDoubleQuote = false;
102487
+ let inVerbatimString = false;
102370
102488
  for (let index = 0; index < line2.length; index++) {
102371
102489
  const char = line2[index];
102372
102490
  const previousChar = index > 0 ? line2[index - 1] : "";
102373
- if (previousChar === "\\") {
102491
+ const nextChar = index + 1 < line2.length ? line2[index + 1] : "";
102492
+ if (inDoubleQuote && inVerbatimString && char === '"' && nextChar === '"') {
102493
+ index++;
102494
+ continue;
102495
+ }
102496
+ if (!inVerbatimString && previousChar === "\\") {
102374
102497
  continue;
102375
102498
  }
102376
102499
  if (char === '"' && !inSingleQuote) {
102377
102500
  inDoubleQuote = !inDoubleQuote;
102501
+ inVerbatimString = inDoubleQuote && previousChar === "@";
102378
102502
  } else if (char === "'" && !inDoubleQuote) {
102379
102503
  inSingleQuote = !inSingleQuote;
102380
102504
  } else if (char === "#" && !inSingleQuote && !inDoubleQuote) {
@@ -102491,8 +102615,8 @@ function extractFileLevelVariables(text) {
102491
102615
  if (isRuntimeComputedVariableValue(value)) {
102492
102616
  continue;
102493
102617
  }
102494
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
102495
- value = value.slice(1, -1);
102618
+ if (isQuotedStringLiteral(value)) {
102619
+ value = decodeQuotedStringLiteral(value);
102496
102620
  }
102497
102621
  variables[match[1]] = value;
102498
102622
  }
@@ -102598,8 +102722,8 @@ function parserHttpRequest(text, variables = {}) {
102598
102722
  requestLine = cleanedLine;
102599
102723
  const [method, ...urlParts] = requestLine.split(" ");
102600
102724
  let url3 = urlParts.join(" ");
102601
- if (url3.startsWith('"') && url3.endsWith('"') || url3.startsWith("'") && url3.endsWith("'")) {
102602
- url3 = url3.slice(1, -1);
102725
+ if (isQuotedStringLiteral(url3)) {
102726
+ url3 = decodeQuotedStringLiteral(url3);
102603
102727
  }
102604
102728
  const headers = {};
102605
102729
  let bodyStartIndex = -1;
@@ -108714,12 +108838,46 @@ function isNornError(error2) {
108714
108838
 
108715
108839
  // src/httpRuntimeOptions.ts
108716
108840
  var verifyTlsCertificates = true;
108841
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
108842
+ var requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
108717
108843
  function setVerifyTlsCertificates(enabled) {
108718
108844
  verifyTlsCertificates = enabled;
108719
108845
  }
108720
108846
  function getVerifyTlsCertificates() {
108721
108847
  return verifyTlsCertificates;
108722
108848
  }
108849
+ function setRequestTimeoutMs(timeoutMs) {
108850
+ if (timeoutMs === void 0) {
108851
+ requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
108852
+ return;
108853
+ }
108854
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
108855
+ throw new Error(`Request timeout must be greater than 0 ms.`);
108856
+ }
108857
+ requestTimeoutMs = Math.ceil(timeoutMs);
108858
+ }
108859
+ function getRequestTimeoutMs() {
108860
+ return requestTimeoutMs;
108861
+ }
108862
+ function parseDurationToMs(rawValue, defaultUnit = "ms") {
108863
+ const trimmed = rawValue.trim().toLowerCase();
108864
+ const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*(ms|milliseconds?|s|sec|secs|seconds?|m|min|mins|minutes?)?$/);
108865
+ if (!match) {
108866
+ return void 0;
108867
+ }
108868
+ const amount = Number(match[1]);
108869
+ if (!Number.isFinite(amount) || amount <= 0) {
108870
+ return void 0;
108871
+ }
108872
+ const unit = match[2] ?? defaultUnit;
108873
+ if (unit === "ms" || unit === "millisecond" || unit === "milliseconds") {
108874
+ return Math.ceil(amount);
108875
+ }
108876
+ if (unit === "m" || unit === "min" || unit === "mins" || unit === "minute" || unit === "minutes") {
108877
+ return Math.ceil(amount * 6e4);
108878
+ }
108879
+ return Math.ceil(amount * 1e3);
108880
+ }
108723
108881
 
108724
108882
  // src/formUrlEncoded.ts
108725
108883
  function parseEqualsField(segment) {
@@ -108910,7 +109068,7 @@ async function sendRequestWithJar(request, jar, retryOptions) {
108910
109068
  adapter: "http",
108911
109069
  headers,
108912
109070
  data,
108913
- timeout: 3e4,
109071
+ timeout: getRequestTimeoutMs(),
108914
109072
  maxRedirects: 0,
108915
109073
  validateStatus: () => true,
108916
109074
  httpsAgent: getHttpsAgent()
@@ -109661,34 +109819,6 @@ function validateAgainstSchemaObjectDetailed(value, schema) {
109661
109819
  }
109662
109820
  }
109663
109821
 
109664
- // src/quotedString.ts
109665
- function decodeQuotedStringLiteral(literal2) {
109666
- if (literal2.length < 2) {
109667
- return literal2;
109668
- }
109669
- const quoteChar = literal2[0];
109670
- if (quoteChar !== '"' && quoteChar !== "'" || literal2[literal2.length - 1] !== quoteChar) {
109671
- return literal2;
109672
- }
109673
- const inner = literal2.slice(1, -1);
109674
- let decoded = "";
109675
- for (let i = 0; i < inner.length; i++) {
109676
- const char = inner[i];
109677
- if (char !== "\\" || i === inner.length - 1) {
109678
- decoded += char;
109679
- continue;
109680
- }
109681
- const nextChar = inner[i + 1];
109682
- if (nextChar === "\\" || nextChar === quoteChar) {
109683
- decoded += nextChar;
109684
- i++;
109685
- continue;
109686
- }
109687
- decoded += char;
109688
- }
109689
- return decoded;
109690
- }
109691
-
109692
109822
  // src/assertionRunner.ts
109693
109823
  function isAssertCommand(line2) {
109694
109824
  return /^assert\s+/i.test(line2.trim());
@@ -109704,17 +109834,25 @@ function parseAssertCommand(line2) {
109704
109834
  const pipeIndex = findUnquotedPipe(content);
109705
109835
  if (pipeIndex !== -1) {
109706
109836
  message = content.substring(pipeIndex + 1).trim();
109707
- if (message.startsWith('"') && message.endsWith('"') || message.startsWith("'") && message.endsWith("'")) {
109837
+ if (isQuotedStringLiteral(message)) {
109838
+ const messageWasVerbatim = isVerbatimStringLiteral(message);
109708
109839
  message = decodeQuotedStringLiteral(message);
109840
+ if (messageWasVerbatim) {
109841
+ return parseAssertContent(content.substring(0, pipeIndex).trim(), message, true);
109842
+ }
109709
109843
  }
109710
109844
  content = content.substring(0, pipeIndex).trim();
109711
109845
  }
109846
+ return parseAssertContent(content, message, false);
109847
+ }
109848
+ function parseAssertContent(content, message, messageIsVerbatim) {
109712
109849
  const existsMatch = content.match(/^(.+?)\s+(exists|!exists)$/i);
109713
109850
  if (existsMatch) {
109714
109851
  return {
109715
109852
  leftExpr: existsMatch[1].trim(),
109716
109853
  operator: existsMatch[2].toLowerCase(),
109717
- message
109854
+ message,
109855
+ messageIsVerbatim
109718
109856
  };
109719
109857
  }
109720
109858
  const binaryOperators = [
@@ -109722,14 +109860,14 @@ function parseAssertCommand(line2) {
109722
109860
  { pattern: /^(.+?)\s*<=\s*(.+)$/, op: "<=" },
109723
109861
  { pattern: /^(.+?)\s*==\s*(.+)$/, op: "==" },
109724
109862
  { pattern: /^(.+?)\s*!=\s*(.+)$/, op: "!=" },
109725
- { pattern: /^(.+?)\s*>\s*(.+)$/, op: ">" },
109726
- { pattern: /^(.+?)\s*<\s*(.+)$/, op: "<" },
109727
109863
  { pattern: /^(.+?)\s+contains\s+(.+)$/i, op: "contains" },
109728
109864
  { pattern: /^(.+?)\s+startsWith\s+(.+)$/i, op: "startsWith" },
109729
109865
  { pattern: /^(.+?)\s+endsWith\s+(.+)$/i, op: "endsWith" },
109730
- { pattern: /^(.+?)\s+matches\s+(.+)$/i, op: "matches" },
109731
109866
  { pattern: /^(.+?)\s+matchesSchema\s+(.+)$/i, op: "matchesSchema" },
109732
- { pattern: /^(.+?)\s+isType\s+(.+)$/i, op: "isType" }
109867
+ { pattern: /^(.+?)\s+matches\s+(.+)$/i, op: "matches" },
109868
+ { pattern: /^(.+?)\s+isType\s+(.+)$/i, op: "isType" },
109869
+ { pattern: /^(.+?)\s*>\s*(.+)$/, op: ">" },
109870
+ { pattern: /^(.+?)\s*<\s*(.+)$/, op: "<" }
109733
109871
  ];
109734
109872
  for (const { pattern, op } of binaryOperators) {
109735
109873
  const binaryMatch = content.match(pattern);
@@ -109738,7 +109876,8 @@ function parseAssertCommand(line2) {
109738
109876
  leftExpr: binaryMatch[1].trim(),
109739
109877
  operator: op,
109740
109878
  rightExpr: binaryMatch[2].trim(),
109741
- message
109879
+ message,
109880
+ messageIsVerbatim
109742
109881
  };
109743
109882
  }
109744
109883
  }
@@ -109748,28 +109887,44 @@ function findUnquotedPipe(str) {
109748
109887
  let inQuote = false;
109749
109888
  let quoteChar = "";
109750
109889
  let escapeNext = false;
109890
+ let inVerbatimString = false;
109751
109891
  for (let i = 0; i < str.length; i++) {
109752
109892
  const char = str[i];
109893
+ if (inQuote && inVerbatimString && char === '"') {
109894
+ if (str[i + 1] === '"') {
109895
+ i++;
109896
+ continue;
109897
+ }
109898
+ if (!isVerbatimAssertionStringTerminator(str, i)) {
109899
+ continue;
109900
+ }
109901
+ }
109753
109902
  if (escapeNext) {
109754
109903
  escapeNext = false;
109755
109904
  continue;
109756
109905
  }
109757
- if (inQuote && char === "\\") {
109906
+ if (inQuote && !inVerbatimString && char === "\\") {
109758
109907
  escapeNext = true;
109759
109908
  continue;
109760
109909
  }
109761
109910
  if ((char === '"' || char === "'") && !inQuote) {
109762
109911
  inQuote = true;
109763
109912
  quoteChar = char;
109913
+ inVerbatimString = char === '"' && i > 0 && str[i - 1] === "@";
109764
109914
  } else if (char === quoteChar && inQuote) {
109765
109915
  inQuote = false;
109766
109916
  quoteChar = "";
109917
+ inVerbatimString = false;
109767
109918
  } else if (char === "|" && !inQuote) {
109768
109919
  return i;
109769
109920
  }
109770
109921
  }
109771
109922
  return -1;
109772
109923
  }
109924
+ function isVerbatimAssertionStringTerminator(str, quoteIndex) {
109925
+ const remainder = str.substring(quoteIndex + 1).trimStart();
109926
+ return remainder === "" || remainder.startsWith("|");
109927
+ }
109773
109928
  function resolveValue(expr, responses, variables, getValueByPath2, responseIndexToVariable) {
109774
109929
  const trimmed = expr.trim();
109775
109930
  const wrappedResponseRefMatch = trimmed.match(/^\{\{(\$\d+(?:\..+)?)\}\}$/);
@@ -109875,8 +110030,9 @@ function resolveValue(expr, responses, variables, getValueByPath2, responseIndex
109875
110030
  }
109876
110031
  return { value: void 0, error: `Variable {{${varName}}} is not defined` };
109877
110032
  }
109878
- if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
109879
- return { value: substituteVariables(decodeQuotedStringLiteral(trimmed), variables) };
110033
+ if (isQuotedStringLiteral(trimmed)) {
110034
+ const decoded = decodeQuotedStringLiteral(trimmed);
110035
+ return { value: isVerbatimStringLiteral(trimmed) ? decoded : substituteVariables(decoded, variables) };
109880
110036
  }
109881
110037
  if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
109882
110038
  return { value: parseFloat(trimmed) };
@@ -109949,7 +110105,7 @@ function areValuesEqual(leftValue, rightValue) {
109949
110105
  }
109950
110106
  function evaluateAssertion(assertion, responses, variables, getValueByPath2, responseIndexToVariable, basePath) {
109951
110107
  const leftResult = resolveValue(assertion.leftExpr, responses, variables, getValueByPath2, responseIndexToVariable);
109952
- const message = assertion.message === void 0 ? void 0 : substituteVariables(assertion.message, variables);
110108
+ const message = assertion.message === void 0 ? void 0 : assertion.messageIsVerbatim ? assertion.message : substituteVariables(assertion.message, variables);
109953
110109
  const expression = formatExpression(assertion, variables);
109954
110110
  const buildFailureContext = () => ({
109955
110111
  responseIndex: leftResult.responseIndex,
@@ -110032,10 +110188,14 @@ function evaluateAssertion(assertion, responses, variables, getValueByPath2, res
110032
110188
  }
110033
110189
  if (assertion.operator === "matchesSchema") {
110034
110190
  let schemaPath = assertion.rightExpr;
110035
- if (schemaPath.startsWith('"') && schemaPath.endsWith('"') || schemaPath.startsWith("'") && schemaPath.endsWith("'")) {
110036
- schemaPath = schemaPath.slice(1, -1);
110191
+ let schemaPathIsVerbatim = false;
110192
+ if (isQuotedStringLiteral(schemaPath)) {
110193
+ schemaPathIsVerbatim = isVerbatimStringLiteral(schemaPath);
110194
+ schemaPath = decodeQuotedStringLiteral(schemaPath);
110195
+ }
110196
+ if (!schemaPathIsVerbatim) {
110197
+ schemaPath = substituteVariables(schemaPath, variables);
110037
110198
  }
110038
- schemaPath = substituteVariables(schemaPath, variables);
110039
110199
  const validationResult = validateAgainstSchemaDetailed(leftValue, schemaPath, basePath);
110040
110200
  const passed2 = validationResult.valid;
110041
110201
  return {
@@ -110102,15 +110262,8 @@ function evaluateAssertion(assertion, responses, variables, getValueByPath2, res
110102
110262
  break;
110103
110263
  case "matches":
110104
110264
  try {
110105
- let pattern = String(rightValue);
110106
- if (pattern.startsWith("/") && pattern.lastIndexOf("/") > 0) {
110107
- const lastSlash = pattern.lastIndexOf("/");
110108
- const flags = pattern.substring(lastSlash + 1);
110109
- pattern = pattern.substring(1, lastSlash);
110110
- passed = new RegExp(pattern, flags).test(String(leftValue));
110111
- } else {
110112
- passed = new RegExp(pattern).test(String(leftValue));
110113
- }
110265
+ const pattern = String(rightValue);
110266
+ passed = new RegExp(pattern).test(String(leftValue));
110114
110267
  } catch (e) {
110115
110268
  return {
110116
110269
  passed: false,
@@ -110146,7 +110299,7 @@ function formatExpression(assertion, variables) {
110146
110299
  return substituteAssertionDisplayTemplates(`${assertion.leftExpr} ${assertion.operator}`, variables);
110147
110300
  }
110148
110301
  function substituteAssertionDisplayTemplates(expression, variables) {
110149
- return variables ? substituteVariables(expression, variables) : expression;
110302
+ return variables ? mapOutsideVerbatimStrings(expression, (segment) => substituteVariables(segment, variables)) : expression;
110150
110303
  }
110151
110304
 
110152
110305
  // src/jsonFileReader.ts
@@ -110163,8 +110316,8 @@ function parseJsonCommand(line2) {
110163
110316
  return null;
110164
110317
  }
110165
110318
  let filePath = match[2].trim();
110166
- if (filePath.startsWith('"') && filePath.endsWith('"') || filePath.startsWith("'") && filePath.endsWith("'")) {
110167
- filePath = filePath.slice(1, -1);
110319
+ if (isQuotedStringLiteral(filePath)) {
110320
+ filePath = decodeQuotedStringLiteral(filePath);
110168
110321
  }
110169
110322
  return {
110170
110323
  varName: match[1],
@@ -110546,6 +110699,18 @@ function isStringRecord(value) {
110546
110699
  function isKnownSection(value) {
110547
110700
  return value === void 0 || isObjectRecord(value);
110548
110701
  }
110702
+ function isNornHttpConfig(value) {
110703
+ if (value === void 0) {
110704
+ return true;
110705
+ }
110706
+ if (!isObjectRecord(value)) {
110707
+ return false;
110708
+ }
110709
+ if (value._comment !== void 0 && typeof value._comment !== "string" && (!Array.isArray(value._comment) || !value._comment.every((item) => typeof item === "string"))) {
110710
+ return false;
110711
+ }
110712
+ return value.timeoutMs === void 0 || typeof value.timeoutMs === "number" && Number.isFinite(value.timeoutMs) && value.timeoutMs > 0;
110713
+ }
110549
110714
  function isNornProjectConfig(value) {
110550
110715
  if (!isObjectRecord(value)) {
110551
110716
  return false;
@@ -110553,7 +110718,7 @@ function isNornProjectConfig(value) {
110553
110718
  if (value.version !== 1) {
110554
110719
  return false;
110555
110720
  }
110556
- return isKnownSection(value.sql) && isKnownSection(value.mcp);
110721
+ return isNornHttpConfig(value.http) && isKnownSection(value.sql) && isKnownSection(value.mcp);
110557
110722
  }
110558
110723
  function loadNornConfig(startPath) {
110559
110724
  const filePath = findNearestConfigFile(startPath, NORN_CONFIG_FILENAME);
@@ -128016,7 +128181,7 @@ function parseRunArguments(argsStr) {
128016
128181
  if (!argsStr || !argsStr.trim()) {
128017
128182
  return args;
128018
128183
  }
128019
- const parts = argsStr.split(/,(?=(?:[^"]*"[^"]*")*(?:[^{]*\{\{[^}]*\}\})*[^"]*$)/);
128184
+ const parts = splitNamedArgumentList(argsStr);
128020
128185
  for (const part of parts) {
128021
128186
  const trimmed = part.trim();
128022
128187
  if (!trimmed) {
@@ -128025,7 +128190,7 @@ function parseRunArguments(argsStr) {
128025
128190
  const namedMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.+)$/);
128026
128191
  if (namedMatch) {
128027
128192
  let value = namedMatch[2].trim();
128028
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
128193
+ if (isQuotedStringLiteral(value)) {
128029
128194
  value = decodeQuotedStringLiteral(value);
128030
128195
  }
128031
128196
  args.push({
@@ -128034,7 +128199,7 @@ function parseRunArguments(argsStr) {
128034
128199
  });
128035
128200
  } else {
128036
128201
  let value = trimmed;
128037
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
128202
+ if (isQuotedStringLiteral(value)) {
128038
128203
  value = decodeQuotedStringLiteral(value);
128039
128204
  }
128040
128205
  args.push({ value });
@@ -128046,6 +128211,7 @@ function splitNamedArgumentList(argsStr) {
128046
128211
  const parts = [];
128047
128212
  let current = "";
128048
128213
  let quoteChar = null;
128214
+ let inVerbatimString = false;
128049
128215
  let templateDepth = 0;
128050
128216
  let bracketDepth = 0;
128051
128217
  for (let i = 0; i < argsStr.length; i++) {
@@ -128054,13 +128220,20 @@ function splitNamedArgumentList(argsStr) {
128054
128220
  const prev = i > 0 ? argsStr[i - 1] : "";
128055
128221
  if (quoteChar) {
128056
128222
  current += char;
128223
+ if (inVerbatimString && char === '"' && next === '"') {
128224
+ current += next;
128225
+ i++;
128226
+ continue;
128227
+ }
128057
128228
  if (char === quoteChar && prev !== "\\") {
128058
128229
  quoteChar = null;
128230
+ inVerbatimString = false;
128059
128231
  }
128060
128232
  continue;
128061
128233
  }
128062
128234
  if (char === '"' || char === "'") {
128063
128235
  quoteChar = char;
128236
+ inVerbatimString = char === '"' && prev === "@";
128064
128237
  current += char;
128065
128238
  continue;
128066
128239
  }
@@ -128520,7 +128693,7 @@ function parseSequenceParameters(line2) {
128520
128693
  if (!paramsStr) {
128521
128694
  return params;
128522
128695
  }
128523
- const paramParts = paramsStr.split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/);
128696
+ const paramParts = splitNamedArgumentList(paramsStr);
128524
128697
  let hasSeenDefault = false;
128525
128698
  for (const part of paramParts) {
128526
128699
  const trimmed = part.trim();
@@ -128531,7 +128704,7 @@ function parseSequenceParameters(line2) {
128531
128704
  if (defaultMatch) {
128532
128705
  hasSeenDefault = true;
128533
128706
  let defaultValue = defaultMatch[2].trim();
128534
- if (defaultValue.startsWith('"') && defaultValue.endsWith('"') || defaultValue.startsWith("'") && defaultValue.endsWith("'")) {
128707
+ if (isQuotedStringLiteral(defaultValue)) {
128535
128708
  defaultValue = decodeQuotedStringLiteral(defaultValue);
128536
128709
  }
128537
128710
  params.push({
@@ -128608,14 +128781,21 @@ function parseDataValues(valuesStr) {
128608
128781
  const values = [];
128609
128782
  let current = "";
128610
128783
  let inQuote = null;
128784
+ let inVerbatimString = false;
128611
128785
  let i = 0;
128612
128786
  while (i < valuesStr.length) {
128613
128787
  const char = valuesStr[i];
128614
128788
  if (inQuote) {
128789
+ if (inVerbatimString && char === '"' && valuesStr[i + 1] === '"') {
128790
+ current += '""';
128791
+ i += 2;
128792
+ continue;
128793
+ }
128615
128794
  if (char === inQuote) {
128616
- values.push(current);
128795
+ values.push(inVerbatimString ? decodeQuotedStringLiteral(`@"${current}"`) : current);
128617
128796
  current = "";
128618
128797
  inQuote = null;
128798
+ inVerbatimString = false;
128619
128799
  i++;
128620
128800
  while (i < valuesStr.length && valuesStr[i] !== ",") {
128621
128801
  i++;
@@ -128626,8 +128806,15 @@ function parseDataValues(valuesStr) {
128626
128806
  current += char;
128627
128807
  }
128628
128808
  } else {
128629
- if (char === '"' || char === "'") {
128809
+ if (char === "@" && valuesStr[i + 1] === '"') {
128810
+ inQuote = '"';
128811
+ inVerbatimString = true;
128812
+ current = "";
128813
+ i += 2;
128814
+ continue;
128815
+ } else if (char === '"' || char === "'") {
128630
128816
  inQuote = char;
128817
+ inVerbatimString = false;
128631
128818
  current = "";
128632
128819
  } else if (char === ",") {
128633
128820
  const trimmed = current.trim();
@@ -128664,7 +128851,7 @@ function parseTypedValue(value) {
128664
128851
  if (/^-?\d+(\.\d+)?$/.test(value)) {
128665
128852
  return parseFloat(value);
128666
128853
  }
128667
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
128854
+ if (isQuotedStringLiteral(value)) {
128668
128855
  return decodeQuotedStringLiteral(value);
128669
128856
  }
128670
128857
  return value;
@@ -128790,9 +128977,9 @@ function parseVarAssignCommand(line2) {
128790
128977
  }
128791
128978
  function evaluateValueExpression(expr, runtimeVariables) {
128792
128979
  const trimmed = expr.trim();
128793
- if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
128980
+ if (isQuotedStringLiteral(trimmed)) {
128794
128981
  const inner = decodeQuotedStringLiteral(trimmed);
128795
- const substituted = substituteVariables(inner, runtimeVariables);
128982
+ const substituted = isVerbatimStringLiteral(trimmed) ? inner : substituteVariables(inner, runtimeVariables);
128796
128983
  return { value: substituted };
128797
128984
  }
128798
128985
  if (trimmed === "true" || trimmed === "false" || trimmed === "null") {
@@ -128840,9 +129027,9 @@ function evaluateSqlArgumentExpression(expr, runtimeVariables) {
128840
129027
  if (!trimmed) {
128841
129028
  return { value: "" };
128842
129029
  }
128843
- if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
129030
+ if (isQuotedStringLiteral(trimmed)) {
128844
129031
  const inner = decodeQuotedStringLiteral(trimmed);
128845
- return { value: substituteVariables(inner, runtimeVariables) };
129032
+ return { value: isVerbatimStringLiteral(trimmed) ? inner : substituteVariables(inner, runtimeVariables) };
128846
129033
  }
128847
129034
  if (trimmed.startsWith("{{") && trimmed.endsWith("}}")) {
128848
129035
  return evaluateSqlArgumentExpression(trimmed.slice(2, -2).trim(), runtimeVariables);
@@ -128952,7 +129139,7 @@ function resolveBareVariables(text, variables) {
128952
129139
  const parts = splitExpressionParts(text);
128953
129140
  const resolvedParts = parts.map((part) => {
128954
129141
  const partTrimmed = part.trim();
128955
- if (partTrimmed.startsWith('"') && partTrimmed.endsWith('"') || partTrimmed.startsWith("'") && partTrimmed.endsWith("'")) {
129142
+ if (isQuotedStringLiteral(partTrimmed)) {
128956
129143
  return decodeQuotedStringLiteral(partTrimmed);
128957
129144
  }
128958
129145
  const varMatch = partTrimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)((?:\.[a-zA-Z_][a-zA-Z0-9_]*|\[\d+\])*)$/);
@@ -128980,6 +129167,7 @@ function splitExpressionParts(expr) {
128980
129167
  let inString = false;
128981
129168
  let stringChar = "";
128982
129169
  let escapeNext = false;
129170
+ let inVerbatimString = false;
128983
129171
  for (let i = 0; i < expr.length; i++) {
128984
129172
  const char = expr[i];
128985
129173
  if (escapeNext) {
@@ -128987,7 +129175,12 @@ function splitExpressionParts(expr) {
128987
129175
  escapeNext = false;
128988
129176
  continue;
128989
129177
  }
128990
- if (inString && char === "\\") {
129178
+ if (inString && inVerbatimString && char === '"' && i + 1 < expr.length && expr[i + 1] === '"') {
129179
+ current += char + expr[i + 1];
129180
+ i++;
129181
+ continue;
129182
+ }
129183
+ if (inString && !inVerbatimString && char === "\\") {
128991
129184
  current += char;
128992
129185
  escapeNext = true;
128993
129186
  continue;
@@ -128995,9 +129188,11 @@ function splitExpressionParts(expr) {
128995
129188
  if (!inString && (char === '"' || char === "'")) {
128996
129189
  inString = true;
128997
129190
  stringChar = char;
129191
+ inVerbatimString = char === '"' && i > 0 && expr[i - 1] === "@";
128998
129192
  current += char;
128999
129193
  } else if (inString && char === stringChar) {
129000
129194
  inString = false;
129195
+ inVerbatimString = false;
129001
129196
  current += char;
129002
129197
  } else if (!inString && char === "+") {
129003
129198
  parts.push(current);
@@ -131444,7 +131639,8 @@ function formatAssertion(assertion, options) {
131444
131639
  if (assertion.error) {
131445
131640
  lines.push(` ${colors.error(`Error: ${assertion.error}`)}`);
131446
131641
  } else if (!assertion.passed) {
131447
- lines.push(` ${colors.dim("Expected:")} ${formatValue(assertion.rightExpression ?? assertion.operator)}`);
131642
+ const expectedValue = assertion.rightValue !== void 0 ? assertion.rightValue : assertion.rightExpression ?? assertion.operator;
131643
+ lines.push(` ${colors.dim("Expected:")} ${formatValue(expectedValue)}`);
131448
131644
  lines.push(` ${colors.dim("Actual:")} ${formatValue(assertion.leftValue)}`);
131449
131645
  if (verbose && assertion.leftExpression !== String(assertion.leftValue)) {
131450
131646
  lines.push(` ${colors.dim("Expression:")} ${assertion.leftExpression}`);
@@ -131738,12 +131934,13 @@ function generateTestCase(sequenceName, assertion, redaction) {
131738
131934
  let xml = ` <testcase name="${escapeXml(testName)}" classname="${escapeXml(className)}">
131739
131935
  `;
131740
131936
  if (!assertion.passed) {
131741
- const message = assertion.error || `Expected ${assertion.rightExpression || assertion.rightValue}, got ${JSON.stringify(assertion.leftValue)}`;
131937
+ const expectedValue = assertion.rightValue !== void 0 ? assertion.rightValue : assertion.rightExpression;
131938
+ const message = assertion.error || `Expected ${JSON.stringify(expectedValue)}, got ${JSON.stringify(assertion.leftValue)}`;
131742
131939
  xml += ` <failure message="${escapeXml(redactString(message, redaction))}" type="AssertionError">
131743
131940
  `;
131744
131941
  xml += `<![CDATA[Expression: ${assertion.expression}
131745
131942
  `;
131746
- xml += `Expected: ${JSON.stringify(assertion.rightValue)}
131943
+ xml += `Expected: ${JSON.stringify(expectedValue)}
131747
131944
  `;
131748
131945
  xml += `Actual: ${JSON.stringify(assertion.leftValue)}]]>
131749
131946
  `;
@@ -132115,11 +132312,13 @@ function generateAssertionHtml(step, redaction) {
132115
132312
  } else {
132116
132313
  actualDisplay = String(assertion.leftValue);
132117
132314
  }
132315
+ const expectedValue = assertion.rightValue !== void 0 ? assertion.rightValue : assertion.rightExpression;
132316
+ const expectedDisplay = typeof expectedValue === "object" ? JSON.stringify(expectedValue, null, 2) : String(expectedValue);
132118
132317
  const pathInfo = assertion.jsonPath ? `<div class="assertion-path"><strong>Path:</strong> <code>${escapeHtml(assertion.jsonPath)}</code></div>` : "";
132119
132318
  detailsHtml = `
132120
132319
  <div class="assertion-details">
132121
132320
  ${pathInfo}
132122
- <div><strong>Expected:</strong> <code>${escapeHtml(String(assertion.rightExpression || assertion.rightValue))}</code></div>
132321
+ <div><strong>Expected:</strong> <code>${escapeHtml(expectedDisplay)}</code></div>
132123
132322
  <div><strong>Actual:</strong> <code>${escapeHtml(actualDisplay)}</code></div>
132124
132323
  ${assertion.error ? `<div class="error"><strong>Error:</strong> ${escapeHtml(redactString(assertion.error, redaction))}</div>` : ""}
132125
132324
  </div>`;
@@ -134058,6 +134257,32 @@ function applyRegionRefactorToText(text, pattern) {
134058
134257
  ` : result;
134059
134258
  }
134060
134259
 
134260
+ // src/requestTimeoutConfig.ts
134261
+ function getProjectRequestTimeoutMs(startPath) {
134262
+ if (!findNearestConfigFile(startPath, NORN_CONFIG_FILENAME)) {
134263
+ return void 0;
134264
+ }
134265
+ const { config: config2 } = loadNornConfig(startPath);
134266
+ return config2.http?.timeoutMs;
134267
+ }
134268
+ function resolveRequestTimeoutMs(startPath, overrideMs) {
134269
+ if (overrideMs !== void 0) {
134270
+ return overrideMs;
134271
+ }
134272
+ if (startPath) {
134273
+ const projectTimeoutMs = getProjectRequestTimeoutMs(startPath);
134274
+ if (projectTimeoutMs !== void 0) {
134275
+ return projectTimeoutMs;
134276
+ }
134277
+ }
134278
+ return DEFAULT_REQUEST_TIMEOUT_MS;
134279
+ }
134280
+ function applyRequestTimeoutForPath(startPath, overrideMs) {
134281
+ const timeoutMs = resolveRequestTimeoutMs(startPath, overrideMs);
134282
+ setRequestTimeoutMs(timeoutMs);
134283
+ return timeoutMs;
134284
+ }
134285
+
134061
134286
  // src/cli.ts
134062
134287
  function handleImportResolutionErrors(errors, colors) {
134063
134288
  const { blockingErrors, warningErrors } = splitImportResolutionErrors(errors);
@@ -134146,6 +134371,14 @@ function buildCliEnvironmentValidationContext(resolvedEnv, selectedEnv) {
134146
134371
  availableEnvironments: resolvedEnv.availableEnvironments
134147
134372
  };
134148
134373
  }
134374
+ function applyCliRequestTimeout(filePath, options, colors) {
134375
+ try {
134376
+ applyRequestTimeoutForPath(filePath, options.timeoutMs);
134377
+ } catch (error2) {
134378
+ console.error(colors.error(`Invalid request timeout configuration: ${error2 instanceof Error ? error2.message : String(error2)}`));
134379
+ process.exit(1);
134380
+ }
134381
+ }
134149
134382
  function generateTimestamp() {
134150
134383
  const now = /* @__PURE__ */ new Date();
134151
134384
  const year = now.getFullYear();
@@ -134189,7 +134422,17 @@ function parseArgs(args) {
134189
134422
  } else if (arg === "--env" || arg === "-e") {
134190
134423
  options.env = args[++i];
134191
134424
  } else if (arg === "--timeout" || arg === "-t") {
134192
- options.timeout = parseInt(args[++i], 10) * 1e3;
134425
+ const rawTimeout = args[++i];
134426
+ if (!rawTimeout) {
134427
+ console.error("Error: --timeout requires a value, e.g. 180s, 3m, or 300000ms.");
134428
+ process.exit(1);
134429
+ }
134430
+ const timeoutMs = parseDurationToMs(rawTimeout, "s");
134431
+ if (timeoutMs === void 0) {
134432
+ console.error(`Error: Invalid timeout '${rawTimeout}'. Use values like 180s, 3m, or 300000ms.`);
134433
+ process.exit(1);
134434
+ }
134435
+ options.timeoutMs = timeoutMs;
134193
134436
  } else if (arg === "--insecure") {
134194
134437
  options.insecure = true;
134195
134438
  } else if (arg === "--refactor-region-pattern" || arg === "--refactor-nornenv-region-pattern") {
@@ -134240,7 +134483,7 @@ Options:
134240
134483
  -s, --sequence <name> Run a specific sequence by name (single file only)
134241
134484
  -r, --request <name> Run a specific named request (single file only)
134242
134485
  -e, --env <name> Use environment from .nornenv (e.g., dev, prod)
134243
- -t, --timeout <sec> Request timeout in seconds (default: no timeout)
134486
+ -t, --timeout <time> Request timeout override (e.g. 180s, 3m, 300000ms; default: norn.config.json or 30s)
134244
134487
  --insecure Disable TLS certificate verification (dev/self-signed only)
134245
134488
  -j, --json Output results as JSON (for CI/CD)
134246
134489
  -v, --verbose Show detailed output (headers, request/response bodies)
@@ -134565,6 +134808,7 @@ async function main() {
134565
134808
  }
134566
134809
  if (options.sequence || options.request) {
134567
134810
  const filePath = filesToRun[0];
134811
+ applyCliRequestTimeout(filePath, options, colors);
134568
134812
  const secretUnlockResult = await ensureCliSecretsUnlocked(filePath);
134569
134813
  if (!secretUnlockResult.ok) {
134570
134814
  if (secretUnlockResult.errors.length > 0) {
@@ -134769,6 +135013,7 @@ ${fileContent}` : fileContent;
134769
135013
  console.log("");
134770
135014
  }
134771
135015
  for (const filePath of filesToRun) {
135016
+ applyCliRequestTimeout(filePath, options, colors);
134772
135017
  const secretUnlockResult = await ensureCliSecretsUnlocked(filePath);
134773
135018
  if (!secretUnlockResult.ok) {
134774
135019
  if (secretUnlockResult.errors.length > 0) {