norn-cli 1.3.1 → 1.3.2

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,21 @@
2
2
 
3
3
  All notable changes to the "Norn" extension will be documented in this file.
4
4
 
5
+ ## [1.3.2] - 2026-02-08
6
+
7
+ ### Improved
8
+ - **Contract Report View**: Enhanced property highlighting in the annotated JSON view
9
+ - Color-coded property highlights (green=valid, yellow=warning, red=error)
10
+ - Highlights wrap only the property content, not the entire line
11
+ - Selection-style highlighting similar to assert hover
12
+ - Improved indentation and spacing
13
+
14
+ ### Fixed
15
+ - **Syntax Highlighting**: Fixed JSON body coloring after nested objects
16
+ - Properties following `{ }` objects now correctly colored
17
+ - **matchesSchema Keyword**: Fixed partial coloring issue
18
+ - Entire keyword now highlights as operator (was matching "matches" first)
19
+
5
20
  ## [1.3.1] - 2026-02-08
6
21
 
7
22
  - **Clickable JSON Properties**: Click any JSON value in the response panel to generate an assertion
package/README.md CHANGED
@@ -28,6 +28,8 @@ A powerful REST client extension for VS Code with sequences, assertions, environ
28
28
  - **Script Execution**: Run bash, PowerShell, or JavaScript scripts within sequences
29
29
  - **Print Statements**: Debug and log messages during sequence execution
30
30
  - **Cookie Support**: Automatic cookie jar with persistence across requests
31
+ - **Response Comparison**: Compare any two API responses side-by-side with VS Code's diff view
32
+ - **Clickable JSON**: Click any JSON value in the response panel to auto-generate assertions
31
33
  - **Syntax Highlighting**: Full syntax highlighting for requests, headers, JSON bodies
32
34
  - **IntelliSense**: Autocomplete for HTTP methods, headers, variables, and keywords
33
35
  - **Diagnostics**: Error highlighting for undefined variables
@@ -742,6 +744,37 @@ end sequence
742
744
 
743
745
  Use `print "Title" | "Body content"` for expandable messages in the result view.
744
746
 
747
+ ### Response Panel Tools
748
+
749
+ The response panel provides interactive tools that accelerate test development:
750
+
751
+ #### Response Comparison
752
+
753
+ Compare any two API responses side-by-side using VS Code's built-in diff view:
754
+
755
+ 1. Click **⇄ Compare** on the first response body
756
+ 2. Click **⇄ Compare** on the second response
757
+ 3. VS Code opens a diff view showing differences between the two responses
758
+
759
+ This is useful for comparing responses across environments, before/after changes, or expected vs actual results.
760
+
761
+ #### Clickable JSON Values
762
+
763
+ Generate assertions instantly by clicking on JSON values in the response:
764
+
765
+ 1. Expand a response body in the panel
766
+ 2. Hover over any value (string, number, boolean, null) - it highlights with a **+** badge
767
+ 3. Click to automatically insert an assertion at the end of your sequence
768
+
769
+ For example, clicking on `"active"` in `{"status": "active"}` generates:
770
+ ```bash
771
+ assert $response.body.status == "active"
772
+ ```
773
+
774
+ Works with nested paths and arrays:
775
+ - `body.user.email` → `assert $user.body.user.email == "john@example.com"`
776
+ - `body.items[0].id` → `assert $order.body.items[0].id == 123`
777
+
745
778
  ## CLI Usage
746
779
 
747
780
  Run tests from the command line for CI/CD pipelines. Only sequences marked with `test` are executed.
package/dist/cli.js CHANGED
@@ -18582,16 +18582,136 @@ var require_ajv = __commonJS({
18582
18582
  });
18583
18583
 
18584
18584
  // src/schemaGenerator.ts
18585
- function validateAgainstSchema(value, schemaPath, basePath) {
18586
- try {
18587
- let resolvedPath = schemaPath;
18588
- if (!path2.isAbsolute(schemaPath) && basePath) {
18589
- resolvedPath = path2.resolve(basePath, schemaPath);
18585
+ function resolveSchemaPath(schemaPath, basePath, workspaceRoot) {
18586
+ if (schemaPath.startsWith(PATH_ALIAS_PREFIX)) {
18587
+ const relativePath = schemaPath.slice(PATH_ALIAS_PREFIX.length);
18588
+ if (workspaceRoot) {
18589
+ return path2.resolve(workspaceRoot, DEFAULT_CONTRACTS_FOLDER, relativePath);
18590
+ }
18591
+ if (basePath) {
18592
+ let currentDir = basePath;
18593
+ let attempts = 0;
18594
+ while (attempts < 10) {
18595
+ const contractsPath = path2.join(currentDir, DEFAULT_CONTRACTS_FOLDER, relativePath);
18596
+ if (fs.existsSync(path2.dirname(contractsPath))) {
18597
+ return contractsPath;
18598
+ }
18599
+ const parentDir = path2.dirname(currentDir);
18600
+ if (parentDir === currentDir) break;
18601
+ currentDir = parentDir;
18602
+ attempts++;
18603
+ }
18604
+ }
18605
+ if (basePath) {
18606
+ return path2.resolve(basePath, DEFAULT_CONTRACTS_FOLDER, relativePath);
18607
+ }
18608
+ return schemaPath;
18609
+ }
18610
+ if (path2.isAbsolute(schemaPath)) {
18611
+ return schemaPath;
18612
+ }
18613
+ if (basePath) {
18614
+ return path2.resolve(basePath, schemaPath);
18615
+ }
18616
+ return schemaPath;
18617
+ }
18618
+ function convertAjvError(err, value) {
18619
+ const instancePath = err.instancePath || "(root)";
18620
+ let expected;
18621
+ let actual = void 0;
18622
+ switch (err.keyword) {
18623
+ case "type":
18624
+ expected = err.params.type;
18625
+ actual = getValueAtPath(value, instancePath);
18626
+ break;
18627
+ case "required":
18628
+ expected = `property "${err.params.missingProperty}" to exist`;
18629
+ break;
18630
+ case "enum":
18631
+ expected = `one of: ${err.params.allowedValues?.join(", ")}`;
18632
+ actual = getValueAtPath(value, instancePath);
18633
+ break;
18634
+ case "pattern":
18635
+ expected = `to match pattern "${err.params.pattern}"`;
18636
+ actual = getValueAtPath(value, instancePath);
18637
+ break;
18638
+ case "minLength":
18639
+ expected = `length >= ${err.params.limit}`;
18640
+ actual = getValueAtPath(value, instancePath);
18641
+ break;
18642
+ case "maxLength":
18643
+ expected = `length <= ${err.params.limit}`;
18644
+ actual = getValueAtPath(value, instancePath);
18645
+ break;
18646
+ case "minimum":
18647
+ case "exclusiveMinimum":
18648
+ expected = `>= ${err.params.limit}`;
18649
+ actual = getValueAtPath(value, instancePath);
18650
+ break;
18651
+ case "maximum":
18652
+ case "exclusiveMaximum":
18653
+ expected = `<= ${err.params.limit}`;
18654
+ actual = getValueAtPath(value, instancePath);
18655
+ break;
18656
+ case "additionalProperties":
18657
+ expected = `no additional property "${err.params.additionalProperty}"`;
18658
+ break;
18659
+ case "format":
18660
+ expected = `format "${err.params.format}"`;
18661
+ actual = getValueAtPath(value, instancePath);
18662
+ break;
18663
+ default:
18664
+ if (err.params) {
18665
+ expected = JSON.stringify(err.params);
18666
+ }
18667
+ actual = getValueAtPath(value, instancePath);
18668
+ }
18669
+ let severity = "error";
18670
+ if (err.keyword === "additionalProperties") {
18671
+ severity = "warning";
18672
+ }
18673
+ return {
18674
+ instancePath,
18675
+ schemaPath: err.schemaPath,
18676
+ keyword: err.keyword,
18677
+ message: err.message || "Validation failed",
18678
+ params: err.params,
18679
+ expected,
18680
+ actual,
18681
+ severity
18682
+ };
18683
+ }
18684
+ function getValueAtPath(obj, jsonPointer) {
18685
+ if (jsonPointer === "(root)" || jsonPointer === "" || jsonPointer === "/") {
18686
+ return obj;
18687
+ }
18688
+ const parts = jsonPointer.replace(/^\//, "").split("/");
18689
+ let current = obj;
18690
+ for (const part of parts) {
18691
+ if (current === null || current === void 0) {
18692
+ return void 0;
18590
18693
  }
18694
+ const key = part.replace(/~1/g, "/").replace(/~0/g, "~");
18695
+ current = current[key];
18696
+ }
18697
+ return current;
18698
+ }
18699
+ function validateAgainstSchemaDetailed(value, schemaPath, basePath, workspaceRoot) {
18700
+ try {
18701
+ const resolvedPath = resolveSchemaPath(schemaPath, basePath, workspaceRoot);
18591
18702
  if (!fs.existsSync(resolvedPath)) {
18592
18703
  return {
18593
18704
  valid: false,
18594
- errors: [`Schema file not found: ${schemaPath}`]
18705
+ errors: [{
18706
+ instancePath: "(root)",
18707
+ schemaPath: "",
18708
+ keyword: "file",
18709
+ message: `Schema file not found: ${schemaPath}`,
18710
+ params: { path: schemaPath },
18711
+ severity: "error"
18712
+ }],
18713
+ errorStrings: [`Schema file not found: ${schemaPath}`],
18714
+ resolvedSchemaPath: resolvedPath
18595
18715
  };
18596
18716
  }
18597
18717
  const schemaContent = fs.readFileSync(resolvedPath, "utf-8");
@@ -18601,25 +18721,56 @@ function validateAgainstSchema(value, schemaPath, basePath) {
18601
18721
  } catch (e) {
18602
18722
  return {
18603
18723
  valid: false,
18604
- errors: [`Invalid JSON in schema file: ${schemaPath}`]
18724
+ errors: [{
18725
+ instancePath: "(root)",
18726
+ schemaPath: "",
18727
+ keyword: "parse",
18728
+ message: `Invalid JSON in schema file: ${schemaPath}`,
18729
+ params: { path: schemaPath },
18730
+ severity: "error"
18731
+ }],
18732
+ errorStrings: [`Invalid JSON in schema file: ${schemaPath}`],
18733
+ resolvedSchemaPath: resolvedPath
18605
18734
  };
18606
18735
  }
18607
18736
  const validate2 = ajv.compile(schema);
18608
18737
  const valid = validate2(value);
18609
18738
  if (!valid && validate2.errors) {
18610
- const errors = validate2.errors.map((err) => {
18739
+ const errors = validate2.errors.map((err) => convertAjvError(err, value));
18740
+ const errorStrings = validate2.errors.map((err) => {
18611
18741
  const path5 = err.instancePath || "(root)";
18612
18742
  return `${path5}: ${err.message}`;
18613
18743
  });
18614
- return { valid: false, errors };
18744
+ return {
18745
+ valid: false,
18746
+ errors,
18747
+ errorStrings,
18748
+ resolvedSchemaPath: resolvedPath,
18749
+ schema
18750
+ };
18615
18751
  }
18616
- return { valid: true };
18752
+ return {
18753
+ valid: true,
18754
+ resolvedSchemaPath: resolvedPath,
18755
+ schema
18756
+ };
18617
18757
  } catch (e) {
18618
18758
  const error = e instanceof Error ? e.message : String(e);
18619
- return { valid: false, errors: [`Schema validation error: ${error}`] };
18759
+ return {
18760
+ valid: false,
18761
+ errors: [{
18762
+ instancePath: "(root)",
18763
+ schemaPath: "",
18764
+ keyword: "exception",
18765
+ message: `Schema validation error: ${error}`,
18766
+ params: {},
18767
+ severity: "error"
18768
+ }],
18769
+ errorStrings: [`Schema validation error: ${error}`]
18770
+ };
18620
18771
  }
18621
18772
  }
18622
- var import_ajv, fs, path2, ajv;
18773
+ var import_ajv, fs, path2, ajv, PATH_ALIAS_PREFIX, DEFAULT_CONTRACTS_FOLDER;
18623
18774
  var init_schemaGenerator = __esm({
18624
18775
  "src/schemaGenerator.ts"() {
18625
18776
  "use strict";
@@ -18627,6 +18778,8 @@ var init_schemaGenerator = __esm({
18627
18778
  fs = __toESM(require("fs"));
18628
18779
  path2 = __toESM(require("path"));
18629
18780
  ajv = new import_ajv.default({ allErrors: true, strict: false });
18781
+ PATH_ALIAS_PREFIX = "@/";
18782
+ DEFAULT_CONTRACTS_FOLDER = "contracts";
18630
18783
  }
18631
18784
  });
18632
18785
 
@@ -18910,7 +19063,7 @@ function evaluateAssertion(assertion, responses, variables, getValueByPath2, res
18910
19063
  if (schemaPath.startsWith('"') && schemaPath.endsWith('"') || schemaPath.startsWith("'") && schemaPath.endsWith("'")) {
18911
19064
  schemaPath = schemaPath.slice(1, -1);
18912
19065
  }
18913
- const validationResult = validateAgainstSchema(leftValue, schemaPath, basePath);
19066
+ const validationResult = validateAgainstSchemaDetailed(leftValue, schemaPath, basePath);
18914
19067
  const passed2 = validationResult.valid;
18915
19068
  return {
18916
19069
  passed: passed2,
@@ -18921,7 +19074,10 @@ function evaluateAssertion(assertion, responses, variables, getValueByPath2, res
18921
19074
  rightValue: schemaPath,
18922
19075
  leftExpression: assertion.leftExpr,
18923
19076
  rightExpression: assertion.rightExpr,
18924
- error: !passed2 && validationResult.errors ? validationResult.errors.join("; ") : void 0,
19077
+ error: !passed2 && validationResult.errorStrings ? validationResult.errorStrings.join("; ") : void 0,
19078
+ schemaErrors: validationResult.errors,
19079
+ schemaPath: validationResult.resolvedSchemaPath,
19080
+ schema: validationResult.schema,
18925
19081
  ...!passed2 ? buildFailureContext() : {}
18926
19082
  };
18927
19083
  }
@@ -27691,7 +27847,8 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
27691
27847
  const stepResult = {
27692
27848
  type: "assertion",
27693
27849
  stepIndex: stepIdx,
27694
- assertion: result
27850
+ assertion: result,
27851
+ lineNumber: step.lineNumber
27695
27852
  };
27696
27853
  orderedSteps.push(stepResult);
27697
27854
  const statusIcon = result.passed ? "\u2713" : "\u2717";
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.3.1",
5
+ "version": "1.3.2",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"