norn-cli 1.1.3 → 1.2.1

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,40 @@
2
2
 
3
3
  All notable changes to the "Norn" extension will be documented in this file.
4
4
 
5
+ ## [1.2.1] - 2026-02-03
6
+
7
+ ### Added
8
+ - **Inline headers for API endpoints**: Stack custom headers below endpoint calls from `.nornapi` files
9
+ - Example: `GET Login` followed by `x-api-key: "my-key"` on the next line
10
+ - Mix inline headers with header groups (e.g., `Common`)
11
+ - Inline headers take precedence over header group values
12
+ - Syntax highlighting for inline headers after API endpoint calls
13
+
14
+ ### Improved
15
+ - **IntelliSense for header values**: After selecting `Content-Type`, IntelliSense automatically triggers to show content type options (`application/json`, etc.)
16
+ - **Removed snippet tab stops**: Header completions no longer leave you in snippet mode requiring Tab to exit
17
+ - **Better trigger characters**: Added `:` as trigger character for immediate header value suggestions
18
+
19
+ ## [1.2.0] - 2026-02-02
20
+
21
+ ### Added
22
+ - **Swagger API Coverage**: See percentage coverage of your OpenAPI/Swagger endpoints
23
+ - Status bar indicator shows coverage percentage when swagger specs are detected
24
+ - Click to open detailed coverage panel with per-endpoint breakdown
25
+ - Coverage tracks asserted status codes (200, 400, 404, etc.) per endpoint
26
+ - Supports wildcard matching (2xx, 4xx, 5xx)
27
+ - Only counts `test sequence` blocks toward coverage
28
+ - CodeLens on swagger lines shows coverage at a glance
29
+ - Automatic recalculation after test execution
30
+ - Caches swagger specs for performance
31
+
32
+ ## [1.1.4] - 2026-02-02
33
+
34
+ ### Fixed
35
+ - **Inline comments**: Fixed syntax highlighting and execution for inline comments
36
+ - **URL highlighting**: URLs now colored as strings with variable interpolation support
37
+ - **Duplicate detection**: Diagnostics for duplicate variables, named requests, and sequences
38
+
5
39
  ## [1.1.3] - 2026-02-01
6
40
 
7
41
  ### Fixed
package/README.md CHANGED
@@ -14,6 +14,7 @@ A powerful REST client extension for VS Code with sequences, assertions, environ
14
14
  - **Sequences**: Chain multiple requests with response capture using `$N.path`
15
15
  - **Test Sequences**: Mark sequences as tests with `test sequence` for CLI execution
16
16
  - **Test Explorer**: Run tests from VS Code's Testing sidebar with colorful output
17
+ - **Swagger Coverage**: Track API coverage with status bar indicator and detailed panel
17
18
  - **Parameterized Tests**: Data-driven testing with `@data` and `@theory` annotations
18
19
  - **Sequence Tags**: Tag sequences with `@smoke`, `@team(CustomerExp)` for filtering in CI/CD
19
20
  - **Secret Variables**: Mark sensitive environment variables with `secret` for automatic redaction
@@ -841,6 +842,41 @@ jobs:
841
842
 
842
843
  ## Syntax Reference
843
844
 
845
+ ### Swagger API Coverage
846
+
847
+ Track how much of your OpenAPI/Swagger spec is covered by tests:
848
+
849
+ - **Status Bar**: Shows coverage percentage when a `.nornapi` file has a `swagger` URL
850
+ - **Coverage Panel**: Click the status bar to see detailed per-endpoint coverage
851
+ - **Per Status Code**: Each response code (200, 400, 404) counts separately toward 100%
852
+ - **Wildcard Support**: Assert `2xx` to match 200, 201, 204, etc.
853
+ - **Test Sequences Only**: Only `test sequence` blocks count toward coverage
854
+ - **CodeLens**: Coverage shown on swagger import lines
855
+
856
+ Coverage is calculated by analyzing your test sequences for:
857
+ 1. API calls by endpoint name (e.g., `GET GetPetById`)
858
+ 2. Status assertions (e.g., `assert $1.status == 200`)
859
+
860
+ ```bash
861
+ # In your .nornapi file:
862
+ swagger https://petstore.swagger.io/v2/swagger.json
863
+
864
+ GetOrderById: GET https://petstore.swagger.io/v2/store/order/{orderId}
865
+ ```
866
+
867
+ ```bash
868
+ # In your .norn test file:
869
+ test sequence OrderTests
870
+ # This covers GET /store/order/{orderId} with 200
871
+ var order = GET GetOrderById(1)
872
+ assert order.status == 200
873
+
874
+ # This covers GET /store/order/{orderId} with 404
875
+ var notFound = GET GetOrderById(999999)
876
+ assert notFound.status == 404
877
+ end sequence
878
+ ```
879
+
844
880
  | Syntax | Description |
845
881
  |--------|-------------|
846
882
  | `var name = value` | Declare a variable (literal) |
package/dist/cli.js CHANGED
@@ -13159,6 +13159,7 @@ function parseApiRequest(requestContent, endpoints, headerGroups) {
13159
13159
  }
13160
13160
  }
13161
13161
  const headerGroupNames = [];
13162
+ const inlineHeaders = {};
13162
13163
  if (headerGroupsOnLine) {
13163
13164
  const names = headerGroupsOnLine.split(/\s+/).filter((n) => n);
13164
13165
  for (const name of names) {
@@ -13168,13 +13169,28 @@ function parseApiRequest(requestContent, endpoints, headerGroups) {
13168
13169
  }
13169
13170
  }
13170
13171
  const bodyLines = [];
13172
+ let inBodySection = false;
13171
13173
  for (let i = 1; i < lines.length; i++) {
13172
13174
  const line2 = lines[i];
13175
+ if (inBodySection) {
13176
+ bodyLines.push(line2);
13177
+ continue;
13178
+ }
13173
13179
  if (headerGroups.some((hg) => hg.name === line2)) {
13174
13180
  headerGroupNames.push(line2);
13175
- } else {
13176
- bodyLines.push(line2);
13181
+ continue;
13177
13182
  }
13183
+ const headerMatch = line2.match(/^([a-zA-Z][a-zA-Z0-9\-]*)\s*:\s*(.+)$/);
13184
+ if (headerMatch) {
13185
+ let headerValue = headerMatch[2].trim();
13186
+ if (headerValue.startsWith('"') && headerValue.endsWith('"') || headerValue.startsWith("'") && headerValue.endsWith("'")) {
13187
+ headerValue = headerValue.slice(1, -1);
13188
+ }
13189
+ inlineHeaders[headerMatch[1]] = headerValue;
13190
+ continue;
13191
+ }
13192
+ inBodySection = true;
13193
+ bodyLines.push(line2);
13178
13194
  }
13179
13195
  const body = bodyLines.length > 0 ? bodyLines.join("\n") : void 0;
13180
13196
  return {
@@ -13182,6 +13198,7 @@ function parseApiRequest(requestContent, endpoints, headerGroups) {
13182
13198
  endpointName,
13183
13199
  params,
13184
13200
  headerGroupNames,
13201
+ inlineHeaders,
13185
13202
  body
13186
13203
  };
13187
13204
  }
@@ -13219,6 +13236,25 @@ function unquote(value) {
13219
13236
  }
13220
13237
 
13221
13238
  // src/parser.ts
13239
+ function stripInlineComment(line2) {
13240
+ let inSingleQuote = false;
13241
+ let inDoubleQuote = false;
13242
+ for (let i = 0; i < line2.length; i++) {
13243
+ const char = line2[i];
13244
+ const prevChar = i > 0 ? line2[i - 1] : "";
13245
+ if (prevChar === "\\") {
13246
+ continue;
13247
+ }
13248
+ if (char === '"' && !inSingleQuote) {
13249
+ inDoubleQuote = !inDoubleQuote;
13250
+ } else if (char === "'" && !inDoubleQuote) {
13251
+ inSingleQuote = !inSingleQuote;
13252
+ } else if (char === "#" && !inSingleQuote && !inDoubleQuote) {
13253
+ return line2.substring(0, i).trimEnd();
13254
+ }
13255
+ }
13256
+ return line2;
13257
+ }
13222
13258
  function extractNamedRequests(text) {
13223
13259
  const lines = text.split("\n");
13224
13260
  const namedRequests = [];
@@ -13374,9 +13410,12 @@ function parserHttpRequest(text, variables = {}) {
13374
13410
  if (requestLineIndex === -1) {
13375
13411
  throw new Error("No valid HTTP method found");
13376
13412
  }
13377
- const requestLine = allLines[requestLineIndex].trim();
13413
+ const requestLine = stripInlineComment(allLines[requestLineIndex].trim());
13378
13414
  const [method, ...urlParts] = requestLine.split(" ");
13379
- const url2 = urlParts.join(" ");
13415
+ let url2 = urlParts.join(" ");
13416
+ if (url2.startsWith('"') && url2.endsWith('"') || url2.startsWith("'") && url2.endsWith("'")) {
13417
+ url2 = url2.slice(1, -1);
13418
+ }
13380
13419
  const headers = {};
13381
13420
  let bodyStartIndex = -1;
13382
13421
  let foundBlankLine = false;
@@ -20213,8 +20252,9 @@ function extractSequences(text) {
20213
20252
  let currentSequence = null;
20214
20253
  for (let i = 0; i < lines.length; i++) {
20215
20254
  const line2 = lines[i].trim();
20216
- const testSequenceMatch = line2.match(/^test\s+sequence\s+([a-zA-Z_][a-zA-Z0-9_-]*)(?:\s*\([^)]*\))?\s*$/);
20217
- const sequenceMatch = line2.match(/^sequence\s+([a-zA-Z_][a-zA-Z0-9_-]*)(?:\s*\([^)]*\))?\s*$/);
20255
+ const lineWithoutComment = stripInlineComment2(line2);
20256
+ const testSequenceMatch = lineWithoutComment.match(/^test\s+sequence\s+([a-zA-Z_][a-zA-Z0-9_-]*)(?:\s*\([^)]*\))?\s*$/);
20257
+ const sequenceMatch = lineWithoutComment.match(/^sequence\s+([a-zA-Z_][a-zA-Z0-9_-]*)(?:\s*\([^)]*\))?\s*$/);
20218
20258
  if (testSequenceMatch || sequenceMatch) {
20219
20259
  const isTest = !!testSequenceMatch;
20220
20260
  const name = isTest ? testSequenceMatch[1] : sequenceMatch[1];
@@ -20377,11 +20417,31 @@ function evaluateValueExpression(expr, runtimeVariables) {
20377
20417
  }
20378
20418
  return { value: varValue };
20379
20419
  }
20420
+ function stripInlineComment2(line2) {
20421
+ let inSingleQuote = false;
20422
+ let inDoubleQuote = false;
20423
+ for (let i = 0; i < line2.length; i++) {
20424
+ const char = line2[i];
20425
+ const prevChar = i > 0 ? line2[i - 1] : "";
20426
+ if (prevChar === "\\") {
20427
+ continue;
20428
+ }
20429
+ if (char === '"' && !inSingleQuote) {
20430
+ inDoubleQuote = !inDoubleQuote;
20431
+ } else if (char === "'" && !inDoubleQuote) {
20432
+ inSingleQuote = !inSingleQuote;
20433
+ } else if (char === "#" && !inSingleQuote && !inDoubleQuote) {
20434
+ return line2.substring(0, i).trimEnd();
20435
+ }
20436
+ }
20437
+ return line2;
20438
+ }
20380
20439
  function isPrintCommand(line2) {
20381
20440
  return /^print\s+/i.test(line2.trim());
20382
20441
  }
20383
20442
  function parsePrintCommand(line2) {
20384
- const match = line2.trim().match(/^print\s+(.+)$/i);
20443
+ const lineWithoutComment = stripInlineComment2(line2);
20444
+ const match = lineWithoutComment.trim().match(/^print\s+(.+)$/i);
20385
20445
  if (!match) {
20386
20446
  return { title: "" };
20387
20447
  }
@@ -21747,6 +21807,9 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
21747
21807
  Object.assign(combinedHeaders, resolvedHeaders);
21748
21808
  }
21749
21809
  }
21810
+ for (const [headerName, headerValue] of Object.entries(apiRequest.inlineHeaders)) {
21811
+ combinedHeaders[headerName] = substituteVariables(headerValue, runtimeVariables);
21812
+ }
21750
21813
  let resolvedBody;
21751
21814
  if (apiRequest.body) {
21752
21815
  resolvedBody = substituteVariables(apiRequest.body, runtimeVariables);
@@ -23097,6 +23160,13 @@ async function runSingleRequest(fileContent, variables, cookieJar, apiDefinition
23097
23160
  Object.assign(combinedHeaders, resolvedHeaders);
23098
23161
  }
23099
23162
  }
23163
+ for (const [headerName, headerValue] of Object.entries(apiRequest.inlineHeaders)) {
23164
+ let resolved = headerValue;
23165
+ resolved = resolved.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (_, varName) => {
23166
+ return variables[varName] !== void 0 ? String(variables[varName]) : `{{${varName}}}`;
23167
+ });
23168
+ combinedHeaders[headerName] = resolved;
23169
+ }
23100
23170
  const parsed2 = {
23101
23171
  method: apiRequest.method,
23102
23172
  url: resolvedPath,
Binary file
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.1.3",
5
+ "version": "1.2.1",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"
@@ -68,6 +68,16 @@
68
68
  "command": "norn.selectEnvironment",
69
69
  "title": "Select Environment",
70
70
  "category": "Norn"
71
+ },
72
+ {
73
+ "command": "norn.showCoverage",
74
+ "title": "Show API Coverage",
75
+ "category": "Norn"
76
+ },
77
+ {
78
+ "command": "norn.refreshCoverage",
79
+ "title": "Refresh API Coverage",
80
+ "category": "Norn"
71
81
  }
72
82
  ],
73
83
  "languages": [