norn-cli 1.2.2 → 1.2.4

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/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  All notable changes to the "Norn" extension will be documented in this file.
4
4
 
5
+ ## [1.2.4] - 2026-02-07
6
+
7
+ ### Improved
8
+ - **Enhanced failure experience**: Failed assertions now display much richer context
9
+ - **Friendly variable names**: Shows `user.body.status` instead of `$1.body.status` when response was assigned to a variable
10
+ - **Auto-expand failures**: Failed assertions automatically expand to show details
11
+ - **Inline response preview**: Failed assertions include a collapsible preview of the related response body
12
+ - **JSON path highlighting**: The failing field is highlighted in the response preview
13
+ - **Request labels**: Request steps in sequences show variable names (e.g., `user`) instead of `$1`
14
+ - Same improvements apply to CLI output and Test Explorer
15
+
16
+ ## [1.2.3] - 2026-02-07
17
+
18
+ ### Added
19
+ - **Duplicate import detection**: Errors shown when importing files with duplicate definitions
20
+ - Header groups and endpoints from `.nornapi` files
21
+ - Named requests and sequences from `.norn` files
22
+ - Blocks execution in CLI, VS Code, and Test Explorer
23
+ - Red squiggly on import line with descriptive error message
24
+
25
+ ### Fixed
26
+ - **.nornenv validation**: `secret var = value` now shows error (use `secret name = value` instead)
27
+ - Syntax highlighting marks invalid pattern in red
28
+ - Diagnostic message explains correct usage
29
+ - **IntelliSense context**: Script types (`bash`, `powershell`, `js`, `readJson`) only appear after `run` keyword
30
+ - **var keyword highlighting**: `var` keyword stays colored while typing variable name
31
+
5
32
  ## [1.2.2] - 2026-02-05
6
33
 
7
34
  ### Added
package/README.md CHANGED
@@ -79,7 +79,7 @@ sequence Example
79
79
  GET https://api.example.com/users/{{userId}}
80
80
  var userName = $1.body.name
81
81
 
82
- print Result | {{message}}, Name: {{userName}}
82
+ print "Result" | "{{message}}, Name: {{userName}}"
83
83
  end sequence
84
84
  ```
85
85
 
@@ -133,14 +133,14 @@ sequence Login
133
133
  Content-Type: application/json
134
134
  {"username": "admin", "password": "secret"}
135
135
  var token = $1.body.accessToken
136
- print Logged in | Token: {{token}}
136
+ print "Logged in" | "Token: {{token}}"
137
137
  end sequence
138
138
 
139
139
  # Define reusable teardown sequence
140
140
  sequence Logout
141
141
  POST {{baseUrl}}/auth/logout
142
142
  Authorization: Bearer {{token}}
143
- print Logged out
143
+ print "Logged out"
144
144
  end sequence
145
145
 
146
146
  # Main test sequence uses setup/teardown
@@ -171,7 +171,7 @@ Sequences can accept parameters with optional default values:
171
171
  ```bash
172
172
  # Required parameter
173
173
  sequence Greet(name)
174
- print Hello, {{name}}!
174
+ print "Hello, {{name}}!"
175
175
  end sequence
176
176
 
177
177
  # Optional parameter with default
@@ -218,8 +218,8 @@ sequence MyTests
218
218
  var user = run FetchUser("123")
219
219
 
220
220
  # Access individual fields
221
- print User name: {{user.name}}
222
- print User email: {{user.email}}
221
+ print "User name" | "{{user.name}}"
222
+ print "User email" | "{{user.email}}"
223
223
 
224
224
  # Use in requests
225
225
  POST {{baseUrl}}/messages
@@ -434,7 +434,7 @@ sequence UserTests
434
434
 
435
435
  # Capture response to variable
436
436
  var user = GET GetUser(1) Json
437
- print User | {{user.body.name}}
437
+ print "User" | "{{user.body.name}}"
438
438
 
439
439
  # Use variables in endpoint parameters
440
440
  var userId = 5
@@ -551,7 +551,7 @@ sequence AuthFlow
551
551
 
552
552
  # Run another named request
553
553
  run GetProfile
554
- print Profile | Welcome, {{$2.body.name}}!
554
+ print "Profile" | "Welcome, {{$2.body.name}}!"
555
555
  end sequence
556
556
  ```
557
557
 
@@ -565,7 +565,7 @@ sequence ConditionalFlow
565
565
 
566
566
  # Execute block only if condition is true
567
567
  if $1.status == 200
568
- print Success | User found!
568
+ print "Success" | "User found!"
569
569
 
570
570
  GET https://api.example.com/users/1/orders
571
571
  assert $2.status == 200
@@ -573,12 +573,12 @@ sequence ConditionalFlow
573
573
 
574
574
  # Check for errors
575
575
  if $1.status == 404
576
- print Error | User not found
576
+ print "Error" | "User not found"
577
577
  end if
578
578
 
579
579
  # Conditions support all assertion operators
580
580
  if $1.body.role == "admin"
581
- print Admin | User has admin privileges
581
+ print "Admin" | "User has admin privileges"
582
582
  end if
583
583
  end sequence
584
584
  ```
@@ -592,7 +592,7 @@ sequence RateLimitedFlow
592
592
  POST https://api.example.com/jobs
593
593
  var jobId = $1.body.id
594
594
 
595
- print Waiting | Job submitted, waiting for completion...
595
+ print "Waiting" | "Job submitted, waiting for completion..."
596
596
 
597
597
  # Wait 2 seconds
598
598
  wait 2s
@@ -616,19 +616,19 @@ sequence DataDrivenTest
616
616
  var config = run readJson ./test-config.json
617
617
 
618
618
  # Access properties
619
- print Config | Using API: {{config.baseUrl}}
619
+ print "Config" | "Using API: {{config.baseUrl}}"
620
620
 
621
621
  # Use in requests
622
622
  GET {{config.baseUrl}}/users/{{config.testUser.id}}
623
623
 
624
624
  # Access nested values and arrays
625
- print First Role | {{config.testUser.roles[0]}}
625
+ print "First Role" | "{{config.testUser.roles[0]}}"
626
626
 
627
627
  # Modify loaded data inline
628
628
  config.baseUrl = https://api.updated.com
629
629
  config.testUser.name = Updated Name
630
630
 
631
- print Updated | New URL: {{config.baseUrl}}
631
+ print "Updated" | "New URL: {{config.baseUrl}}"
632
632
  end sequence
633
633
  ```
634
634
 
@@ -674,7 +674,7 @@ sequence DatabaseQuery
674
674
  var dbResult = run powershell ./scripts/query-db.ps1
675
675
 
676
676
  # Access properties from the JSON output
677
- print User Found | ID: {{dbResult.id}}, Name: {{dbResult.name}}
677
+ print "User Found" | "ID: {{dbResult.id}}, Name: {{dbResult.name}}"
678
678
 
679
679
  # Use in requests
680
680
  GET https://api.example.com/users/{{dbResult.id}}
@@ -697,18 +697,18 @@ Add debug output to your sequences:
697
697
 
698
698
  ```bash
699
699
  sequence DebugFlow
700
- print Starting authentication...
700
+ print "Starting authentication..."
701
701
 
702
702
  POST https://api.example.com/login
703
703
  Content-Type: application/json
704
704
  {"user": "admin"}
705
705
 
706
706
  var token = $1.token
707
- print Token received | Value: {{token}}
707
+ print "Token received" | "Value: {{token}}"
708
708
  end sequence
709
709
  ```
710
710
 
711
- Use `print Title | Body content` for expandable messages in the result view.
711
+ Use `print "Title" | "Body content"` for expandable messages in the result view.
712
712
 
713
713
  ## CLI Usage
714
714
 
@@ -942,8 +942,8 @@ end sequence
942
942
  | `var x = run js ./script.js` | Run script and capture output |
943
943
  | `var data = run readJson ./file.json` | Load JSON file into variable |
944
944
  | `data.property = value` | Update loaded JSON property |
945
- | `print Message` | Print a message |
946
- | `print Title \| Body` | Print with expandable body |
945
+ | `print "Message"` | Print a message |
946
+ | `print "Title" \| "Body"` | Print with expandable body |
947
947
 
948
948
  ### Environments (.nornenv)
949
949
 
package/dist/cli.js CHANGED
@@ -12216,11 +12216,12 @@ function findUnquotedPipe(str) {
12216
12216
  }
12217
12217
  return -1;
12218
12218
  }
12219
- function resolveValue(expr, responses, variables, getValueByPath2) {
12219
+ function resolveValue(expr, responses, variables, getValueByPath2, responseIndexToVariable) {
12220
12220
  const trimmed = expr.trim();
12221
12221
  const refMatch = trimmed.match(/^\$(\d+)\.(.+)$/);
12222
12222
  if (refMatch) {
12223
- const responseIndex = parseInt(refMatch[1], 10) - 1;
12223
+ const responseIdx = parseInt(refMatch[1], 10);
12224
+ const responseIndex = responseIdx - 1;
12224
12225
  const path4 = refMatch[2];
12225
12226
  if (responseIndex < 0 || responseIndex >= responses.length) {
12226
12227
  return {
@@ -12228,8 +12229,15 @@ function resolveValue(expr, responses, variables, getValueByPath2) {
12228
12229
  error: `Response $${refMatch[1]} does not exist (only ${responses.length} responses so far)`
12229
12230
  };
12230
12231
  }
12231
- const value = getValueByPath2(responses[responseIndex], path4);
12232
- return { value };
12232
+ const response = responses[responseIndex];
12233
+ const value = getValueByPath2(response, path4);
12234
+ return {
12235
+ value,
12236
+ responseIndex: responseIdx,
12237
+ response,
12238
+ jsonPath: path4,
12239
+ variableName: responseIndexToVariable?.get(responseIdx)
12240
+ };
12233
12241
  }
12234
12242
  if (/^\$\d+$/.test(trimmed)) {
12235
12243
  return {
@@ -12246,14 +12254,20 @@ function resolveValue(expr, responses, variables, getValueByPath2) {
12246
12254
  if (typeof varValue === "object" && varValue !== null) {
12247
12255
  const path4 = pathPart.replace(/^\./, "");
12248
12256
  const value = getNestedValue2(varValue, path4);
12249
- return { value };
12257
+ const isHttpResponse = "status" in varValue && "body" in varValue;
12258
+ return {
12259
+ value,
12260
+ variableName: varName,
12261
+ jsonPath: path4,
12262
+ response: isHttpResponse ? varValue : void 0
12263
+ };
12250
12264
  }
12251
12265
  if (typeof varValue === "string") {
12252
12266
  try {
12253
12267
  const parsed = JSON.parse(varValue);
12254
12268
  const path4 = pathPart.replace(/^\./, "");
12255
12269
  const value = getNestedValue2(parsed, path4);
12256
- return { value };
12270
+ return { value, variableName: varName, jsonPath: path4 };
12257
12271
  } catch {
12258
12272
  return { value: void 0, error: `Cannot access path on non-object variable: ${varName}` };
12259
12273
  }
@@ -12317,8 +12331,14 @@ function getNestedValue2(obj, path4) {
12317
12331
  }
12318
12332
  return current;
12319
12333
  }
12320
- function evaluateAssertion(assertion, responses, variables, getValueByPath2) {
12321
- const leftResult = resolveValue(assertion.leftExpr, responses, variables, getValueByPath2);
12334
+ function evaluateAssertion(assertion, responses, variables, getValueByPath2, responseIndexToVariable) {
12335
+ const leftResult = resolveValue(assertion.leftExpr, responses, variables, getValueByPath2, responseIndexToVariable);
12336
+ const buildFailureContext = () => ({
12337
+ responseIndex: leftResult.responseIndex,
12338
+ friendlyName: leftResult.variableName,
12339
+ relatedResponse: leftResult.response,
12340
+ jsonPath: leftResult.jsonPath
12341
+ });
12322
12342
  if (leftResult.error) {
12323
12343
  return {
12324
12344
  passed: false,
@@ -12328,28 +12348,33 @@ function evaluateAssertion(assertion, responses, variables, getValueByPath2) {
12328
12348
  leftValue: void 0,
12329
12349
  leftExpression: assertion.leftExpr,
12330
12350
  rightExpression: assertion.rightExpr,
12331
- error: leftResult.error
12351
+ error: leftResult.error,
12352
+ ...buildFailureContext()
12332
12353
  };
12333
12354
  }
12334
12355
  const leftValue = leftResult.value;
12335
12356
  if (assertion.operator === "exists") {
12357
+ const passed2 = leftValue !== void 0 && leftValue !== null;
12336
12358
  return {
12337
- passed: leftValue !== void 0 && leftValue !== null,
12359
+ passed: passed2,
12338
12360
  expression: formatExpression(assertion),
12339
12361
  message: assertion.message,
12340
12362
  operator: assertion.operator,
12341
12363
  leftValue,
12342
- leftExpression: assertion.leftExpr
12364
+ leftExpression: assertion.leftExpr,
12365
+ ...!passed2 ? buildFailureContext() : {}
12343
12366
  };
12344
12367
  }
12345
12368
  if (assertion.operator === "!exists") {
12369
+ const passed2 = leftValue === void 0 || leftValue === null;
12346
12370
  return {
12347
- passed: leftValue === void 0 || leftValue === null,
12371
+ passed: passed2,
12348
12372
  expression: formatExpression(assertion),
12349
12373
  message: assertion.message,
12350
12374
  operator: assertion.operator,
12351
12375
  leftValue,
12352
- leftExpression: assertion.leftExpr
12376
+ leftExpression: assertion.leftExpr,
12377
+ ...!passed2 ? buildFailureContext() : {}
12353
12378
  };
12354
12379
  }
12355
12380
  if (!assertion.rightExpr) {
@@ -12360,7 +12385,8 @@ function evaluateAssertion(assertion, responses, variables, getValueByPath2) {
12360
12385
  operator: assertion.operator,
12361
12386
  leftValue,
12362
12387
  leftExpression: assertion.leftExpr,
12363
- error: `Operator ${assertion.operator} requires a right-hand value`
12388
+ error: `Operator ${assertion.operator} requires a right-hand value`,
12389
+ ...buildFailureContext()
12364
12390
  };
12365
12391
  }
12366
12392
  if (assertion.operator === "isType") {
@@ -12373,18 +12399,20 @@ function evaluateAssertion(assertion, responses, variables, getValueByPath2) {
12373
12399
  } else {
12374
12400
  actualType = typeof leftValue;
12375
12401
  }
12402
+ const passed2 = actualType === expectedType;
12376
12403
  return {
12377
- passed: actualType === expectedType,
12404
+ passed: passed2,
12378
12405
  expression: formatExpression(assertion),
12379
12406
  message: assertion.message,
12380
12407
  operator: assertion.operator,
12381
12408
  leftValue,
12382
12409
  rightValue: expectedType,
12383
12410
  leftExpression: assertion.leftExpr,
12384
- rightExpression: assertion.rightExpr
12411
+ rightExpression: assertion.rightExpr,
12412
+ ...!passed2 ? buildFailureContext() : {}
12385
12413
  };
12386
12414
  }
12387
- const rightResult = resolveValue(assertion.rightExpr, responses, variables, getValueByPath2);
12415
+ const rightResult = resolveValue(assertion.rightExpr, responses, variables, getValueByPath2, responseIndexToVariable);
12388
12416
  if (rightResult.error) {
12389
12417
  return {
12390
12418
  passed: false,
@@ -12395,7 +12423,8 @@ function evaluateAssertion(assertion, responses, variables, getValueByPath2) {
12395
12423
  rightValue: void 0,
12396
12424
  leftExpression: assertion.leftExpr,
12397
12425
  rightExpression: assertion.rightExpr,
12398
- error: rightResult.error
12426
+ error: rightResult.error,
12427
+ ...buildFailureContext()
12399
12428
  };
12400
12429
  }
12401
12430
  const rightValue = rightResult.value;
@@ -12449,7 +12478,8 @@ function evaluateAssertion(assertion, responses, variables, getValueByPath2) {
12449
12478
  rightValue,
12450
12479
  leftExpression: assertion.leftExpr,
12451
12480
  rightExpression: assertion.rightExpr,
12452
- error: `Invalid regex pattern: ${rightValue}`
12481
+ error: `Invalid regex pattern: ${rightValue}`,
12482
+ ...buildFailureContext()
12453
12483
  };
12454
12484
  }
12455
12485
  break;
@@ -12462,7 +12492,8 @@ function evaluateAssertion(assertion, responses, variables, getValueByPath2) {
12462
12492
  leftValue,
12463
12493
  rightValue,
12464
12494
  leftExpression: assertion.leftExpr,
12465
- rightExpression: assertion.rightExpr
12495
+ rightExpression: assertion.rightExpr,
12496
+ ...!passed ? buildFailureContext() : {}
12466
12497
  };
12467
12498
  }
12468
12499
  function formatExpression(assertion) {
@@ -13493,6 +13524,10 @@ async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__
13493
13524
  const resolvedPaths = [];
13494
13525
  const headerGroups = [];
13495
13526
  const endpoints = [];
13527
+ const headerGroupSources = /* @__PURE__ */ new Map();
13528
+ const endpointSources = /* @__PURE__ */ new Map();
13529
+ const namedRequestSources = /* @__PURE__ */ new Map();
13530
+ const sequenceSources = /* @__PURE__ */ new Map();
13496
13531
  for (const imp of imports) {
13497
13532
  const path4 = await import("path");
13498
13533
  const absolutePath = path4.resolve(baseDir, imp.path);
@@ -13510,16 +13545,64 @@ async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__
13510
13545
  resolvedPaths.push(absolutePath);
13511
13546
  if (imp.path.endsWith(".nornapi")) {
13512
13547
  const apiDef = parseNornApiFile(content);
13513
- headerGroups.push(...apiDef.headerGroups);
13514
- endpoints.push(...apiDef.endpoints);
13548
+ for (const group of apiDef.headerGroups) {
13549
+ const existingSource = headerGroupSources.get(group.name);
13550
+ if (existingSource) {
13551
+ errors.push({
13552
+ path: imp.path,
13553
+ error: `Duplicate header group '${group.name}': already defined in '${existingSource}'`,
13554
+ lineNumber: imp.lineNumber
13555
+ });
13556
+ } else {
13557
+ headerGroupSources.set(group.name, imp.path);
13558
+ headerGroups.push(group);
13559
+ }
13560
+ }
13561
+ for (const endpoint of apiDef.endpoints) {
13562
+ const existingSource = endpointSources.get(endpoint.name);
13563
+ if (existingSource) {
13564
+ errors.push({
13565
+ path: imp.path,
13566
+ error: `Duplicate endpoint '${endpoint.name}': already defined in '${existingSource}'`,
13567
+ lineNumber: imp.lineNumber
13568
+ });
13569
+ } else {
13570
+ endpointSources.set(endpoint.name, imp.path);
13571
+ endpoints.push(endpoint);
13572
+ }
13573
+ }
13515
13574
  continue;
13516
13575
  }
13517
13576
  const importDir = path4.dirname(absolutePath);
13518
13577
  const nestedResult = await resolveImports(content, importDir, readFile2, alreadyImported);
13519
13578
  errors.push(...nestedResult.errors);
13520
13579
  resolvedPaths.push(...nestedResult.resolvedPaths);
13521
- headerGroups.push(...nestedResult.headerGroups);
13522
- endpoints.push(...nestedResult.endpoints);
13580
+ for (const group of nestedResult.headerGroups) {
13581
+ const existingSource = headerGroupSources.get(group.name);
13582
+ if (existingSource) {
13583
+ errors.push({
13584
+ path: imp.path,
13585
+ error: `Duplicate header group '${group.name}': already defined in '${existingSource}'`,
13586
+ lineNumber: imp.lineNumber
13587
+ });
13588
+ } else {
13589
+ headerGroupSources.set(group.name, imp.path);
13590
+ headerGroups.push(group);
13591
+ }
13592
+ }
13593
+ for (const endpoint of nestedResult.endpoints) {
13594
+ const existingSource = endpointSources.get(endpoint.name);
13595
+ if (existingSource) {
13596
+ errors.push({
13597
+ path: imp.path,
13598
+ error: `Duplicate endpoint '${endpoint.name}': already defined in '${existingSource}'`,
13599
+ lineNumber: imp.lineNumber
13600
+ });
13601
+ } else {
13602
+ endpointSources.set(endpoint.name, imp.path);
13603
+ endpoints.push(endpoint);
13604
+ }
13605
+ }
13523
13606
  if (nestedResult.importedContent) {
13524
13607
  importedContents.push(nestedResult.importedContent);
13525
13608
  }
@@ -13527,15 +13610,37 @@ async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__
13527
13610
  const importedNamedRequests = extractNamedRequests(content);
13528
13611
  const importedSequences = extractSequencesFromText(content);
13529
13612
  for (const req of importedNamedRequests) {
13530
- const resolvedContent = substituteVariables(req.content, importedVariables);
13531
- importedContents.push(`[${req.name}]
13613
+ const lowerName = req.name.toLowerCase();
13614
+ const existingSource = namedRequestSources.get(lowerName);
13615
+ if (existingSource) {
13616
+ errors.push({
13617
+ path: imp.path,
13618
+ error: `Duplicate named request '${req.name}': already defined in '${existingSource}'`,
13619
+ lineNumber: imp.lineNumber
13620
+ });
13621
+ } else {
13622
+ namedRequestSources.set(lowerName, imp.path);
13623
+ const resolvedContent = substituteVariables(req.content, importedVariables);
13624
+ importedContents.push(`[${req.name}]
13532
13625
  ${resolvedContent}`);
13626
+ }
13533
13627
  }
13534
13628
  for (const seq of importedSequences) {
13535
- const resolvedContent = substituteVariables(seq.content, importedVariables);
13536
- importedContents.push(`sequence ${seq.name}
13629
+ const lowerName = seq.name.toLowerCase();
13630
+ const existingSource = sequenceSources.get(lowerName);
13631
+ if (existingSource) {
13632
+ errors.push({
13633
+ path: imp.path,
13634
+ error: `Duplicate sequence '${seq.name}': already defined in '${existingSource}'`,
13635
+ lineNumber: imp.lineNumber
13636
+ });
13637
+ } else {
13638
+ sequenceSources.set(lowerName, imp.path);
13639
+ const resolvedContent = substituteVariables(seq.content, importedVariables);
13640
+ importedContents.push(`sequence ${seq.name}
13537
13641
  ${resolvedContent}
13538
13642
  end sequence`);
13643
+ }
13539
13644
  }
13540
13645
  } catch (error) {
13541
13646
  errors.push({
@@ -20921,6 +21026,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
20921
21026
  responses.push(response);
20922
21027
  runtimeVariables[`$${responses.length}`] = response;
20923
21028
  };
21029
+ const responseIndexToVariable = /* @__PURE__ */ new Map();
20924
21030
  const steps = extractStepsFromSequence(sequenceContent);
20925
21031
  const captures = extractCaptureDirectives(sequenceContent);
20926
21032
  const totalSteps = steps.length;
@@ -20987,7 +21093,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
20987
21093
  duration: Date.now() - startTime
20988
21094
  };
20989
21095
  }
20990
- const result = evaluateAssertion(parsed, responses, runtimeVariables, getValueByPath);
21096
+ const result = evaluateAssertion(parsed, responses, runtimeVariables, getValueByPath, responseIndexToVariable);
20991
21097
  assertionResults.push(result);
20992
21098
  const stepResult = {
20993
21099
  type: "assertion",
@@ -21284,13 +21390,15 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
21284
21390
  const requestParsed = parserHttpRequest(requestText, runtimeVariables);
21285
21391
  const response = cookieJar ? await sendRequestWithJar(requestParsed, cookieJar) : await sendRequest(requestParsed);
21286
21392
  addResponse(response);
21393
+ responseIndexToVariable.set(responses.length, parsed.varName);
21287
21394
  runtimeVariables[parsed.varName] = response;
21288
21395
  const stepResult = {
21289
21396
  type: "request",
21290
21397
  stepIndex: stepIdx,
21291
21398
  response,
21292
21399
  requestMethod: parsed.method,
21293
- requestUrl: resolvedUrl
21400
+ requestUrl: resolvedUrl,
21401
+ variableName: parsed.varName
21294
21402
  };
21295
21403
  orderedSteps.push(stepResult);
21296
21404
  reportProgress(stepIdx, "request", `var ${parsed.varName} = ${requestDescription}`, stepResult);
@@ -22223,7 +22331,11 @@ function formatAssertion(assertion, options) {
22223
22331
  const { colors, verbose } = options;
22224
22332
  const lines = [];
22225
22333
  const icon = assertion.passed ? colors.checkmark : colors.cross;
22226
- const displayMessage = assertion.message || assertion.expression;
22334
+ let displayExpr = assertion.expression;
22335
+ if (assertion.friendlyName && assertion.responseIndex) {
22336
+ displayExpr = displayExpr.replace("$" + assertion.responseIndex, assertion.friendlyName);
22337
+ }
22338
+ const displayMessage = assertion.message || displayExpr;
22227
22339
  lines.push(`${icon} assert ${displayMessage}`);
22228
22340
  if (!assertion.passed || verbose) {
22229
22341
  if (assertion.error) {
@@ -23403,9 +23515,22 @@ async function main() {
23403
23515
  workingDir,
23404
23516
  async (importPath) => fsPromises.readFile(importPath, "utf-8")
23405
23517
  );
23406
- for (const err of importResult.errors) {
23518
+ const duplicateErrors = importResult.errors.filter(
23519
+ (err) => err.error.includes("Duplicate header group") || err.error.includes("Duplicate endpoint") || err.error.includes("Duplicate named request") || err.error.includes("Duplicate sequence")
23520
+ );
23521
+ const otherErrors = importResult.errors.filter(
23522
+ (err) => !err.error.includes("Duplicate header group") && !err.error.includes("Duplicate endpoint") && !err.error.includes("Duplicate named request") && !err.error.includes("Duplicate sequence")
23523
+ );
23524
+ for (const err of otherErrors) {
23407
23525
  console.error(colors.warning(`Import warning: ${err.path} - ${err.error}`));
23408
23526
  }
23527
+ if (duplicateErrors.length > 0) {
23528
+ console.error(colors.error("Cannot execute - duplicate definitions found:"));
23529
+ for (const err of duplicateErrors) {
23530
+ console.error(colors.error(` ${err.path}: ${err.error}`));
23531
+ }
23532
+ process.exit(1);
23533
+ }
23409
23534
  const fileContentWithImports = importResult.importedContent ? `${importResult.importedContent}
23410
23535
 
23411
23536
  ${fileContent}` : fileContent;
@@ -23512,9 +23637,22 @@ ${fileContent}` : fileContent;
23512
23637
  workingDir,
23513
23638
  async (importPath) => fsPromises.readFile(importPath, "utf-8")
23514
23639
  );
23515
- for (const err of importResult.errors) {
23640
+ const duplicateErrors2 = importResult.errors.filter(
23641
+ (err) => err.error.includes("Duplicate header group") || err.error.includes("Duplicate endpoint") || err.error.includes("Duplicate named request") || err.error.includes("Duplicate sequence")
23642
+ );
23643
+ const otherErrors2 = importResult.errors.filter(
23644
+ (err) => !err.error.includes("Duplicate header group") && !err.error.includes("Duplicate endpoint") && !err.error.includes("Duplicate named request") && !err.error.includes("Duplicate sequence")
23645
+ );
23646
+ for (const err of otherErrors2) {
23516
23647
  console.error(colors.warning(`Import warning: ${err.path} - ${err.error}`));
23517
23648
  }
23649
+ if (duplicateErrors2.length > 0) {
23650
+ console.error(colors.error("Cannot execute - duplicate definitions found:"));
23651
+ for (const err of duplicateErrors2) {
23652
+ console.error(colors.error(` ${err.path}: ${err.error}`));
23653
+ }
23654
+ process.exit(1);
23655
+ }
23518
23656
  const fileContentWithImports = importResult.importedContent ? `${importResult.importedContent}
23519
23657
 
23520
23658
  ${fileContent}` : fileContent;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "norn-cli",
3
3
  "displayName": "Norn - REST Client",
4
4
  "description": "A powerful REST client for making HTTP requests with sequences, variables, scripts, and cookie support",
5
- "version": "1.2.2",
5
+ "version": "1.2.4",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"