norn-cli 1.10.1 → 1.10.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 +17 -0
- package/dist/cli.js +96 -73
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to the "Norn" extension will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.10.2] - 2026-03-15
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Sequence Return + Response Panel UX**:
|
|
11
|
+
- Fixed `var result = run SomeSequence` so single-value returns can capture literal values like `return "Bar"` instead of resolving to empty output.
|
|
12
|
+
- Fixed streaming/final response panel step timers so print/SQL rows show per-step duration instead of elapsed-from-start totals.
|
|
13
|
+
- Fixed the streaming step counter so hidden `wait` steps no longer inflate the top-bar step total.
|
|
14
|
+
- Fixed `.norn` syntax highlighting so quoted string literals after `return` use normal string coloring.
|
|
15
|
+
|
|
16
|
+
- **Request Parsing + CLI Error Output**:
|
|
17
|
+
- Fixed request parsing to stop bodies at the next Norn block boundary, preventing later named requests or sequence declarations from leaking into request bodies.
|
|
18
|
+
- Fixed CLI text and HTML reports to preserve multiline request/sequence error details instead of collapsing them into single-line output.
|
|
19
|
+
|
|
20
|
+
- **API Coverage + Import UX**:
|
|
21
|
+
- Fixed API coverage to follow status assertions on captured request variables and named requests that call `.nornapi` endpoints.
|
|
22
|
+
- Fixed the OpenAPI import section picker so selecting `All` stays mutually exclusive with individual section selections.
|
|
23
|
+
|
|
7
24
|
## [1.7.0] - 2026-03-07
|
|
8
25
|
|
|
9
26
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -101801,6 +101801,16 @@ function getHeaderGroup(definition, name) {
|
|
|
101801
101801
|
function getEndpoint(definition, name) {
|
|
101802
101802
|
return definition.endpoints.find((ep) => ep.name === name);
|
|
101803
101803
|
}
|
|
101804
|
+
function resolveEndpointPath(endpoint, params) {
|
|
101805
|
+
let resolvedPath = endpoint.path;
|
|
101806
|
+
for (const paramName of endpoint.parameters) {
|
|
101807
|
+
const value = params[paramName];
|
|
101808
|
+
if (value !== void 0) {
|
|
101809
|
+
resolvedPath = resolvedPath.replace(`{${paramName}}`, value);
|
|
101810
|
+
}
|
|
101811
|
+
}
|
|
101812
|
+
return resolvedPath;
|
|
101813
|
+
}
|
|
101804
101814
|
function resolveHeaderValues(headerGroup, variables) {
|
|
101805
101815
|
const resolved = {};
|
|
101806
101816
|
for (const [name, value] of Object.entries(headerGroup.headers)) {
|
|
@@ -102283,7 +102293,7 @@ function extractNamedRequests(text) {
|
|
|
102283
102293
|
let endLine = lines.length - 1;
|
|
102284
102294
|
for (let j = contentStartLine; j < lines.length; j++) {
|
|
102285
102295
|
const scanLine = lines[j].trim();
|
|
102286
|
-
if (scanLine
|
|
102296
|
+
if (isNamedRequestDeclarationLine(scanLine) || isSequenceStartDeclaration(scanLine) || isSequenceEndDeclaration(scanLine) || scanLine.startsWith("###")) {
|
|
102287
102297
|
endLine = j - 1;
|
|
102288
102298
|
break;
|
|
102289
102299
|
}
|
|
@@ -102499,10 +102509,9 @@ function parserHttpRequest(text, variables = {}) {
|
|
|
102499
102509
|
let body;
|
|
102500
102510
|
if (bodyStartIndex > 0) {
|
|
102501
102511
|
const bodyLines = [];
|
|
102502
|
-
const boundaryRegex = /^\[.*\]$|^sequence\s+|^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+/i;
|
|
102503
102512
|
for (let i = bodyStartIndex; i < allLines.length; i++) {
|
|
102504
102513
|
const line2 = allLines[i].trim();
|
|
102505
|
-
if (
|
|
102514
|
+
if (isRequestLine(line2) || isNamedRequestDeclarationLine(line2) || isSequenceLine(line2) || isEndSequenceLine(line2) || line2.startsWith("###")) {
|
|
102506
102515
|
break;
|
|
102507
102516
|
}
|
|
102508
102517
|
if (line2 && !line2.startsWith("#") && !line2.startsWith("var ")) {
|
|
@@ -102516,6 +102525,19 @@ function parserHttpRequest(text, variables = {}) {
|
|
|
102516
102525
|
}
|
|
102517
102526
|
return { method: method.toUpperCase(), url: url2, headers, body, retryCount, backoffMs };
|
|
102518
102527
|
}
|
|
102528
|
+
var METHOD_REGEX = /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT)\s+/i;
|
|
102529
|
+
function isRequestLine(line2) {
|
|
102530
|
+
return METHOD_REGEX.test(line2.trim());
|
|
102531
|
+
}
|
|
102532
|
+
function isSequenceLine(line2) {
|
|
102533
|
+
return isSequenceStartDeclaration(line2.trim());
|
|
102534
|
+
}
|
|
102535
|
+
function isNamedRequestDeclarationLine(line2) {
|
|
102536
|
+
return /^\[[^\]]+\]\s*$/.test(line2.trim());
|
|
102537
|
+
}
|
|
102538
|
+
function isEndSequenceLine(line2) {
|
|
102539
|
+
return isSequenceEndDeclaration(line2.trim());
|
|
102540
|
+
}
|
|
102519
102541
|
function extractImports(text) {
|
|
102520
102542
|
const lines = text.split("\n");
|
|
102521
102543
|
const imports = [];
|
|
@@ -110280,6 +110302,10 @@ function resolveReturnExpression(expr, runtimeVariables) {
|
|
|
110280
110302
|
return { key: varName, value: varValue };
|
|
110281
110303
|
}
|
|
110282
110304
|
}
|
|
110305
|
+
function resolveSingleReturnValue(expr, runtimeVariables) {
|
|
110306
|
+
const result = evaluateSqlArgumentExpression(expr, runtimeVariables);
|
|
110307
|
+
return result.error ? "" : result.value;
|
|
110308
|
+
}
|
|
110283
110309
|
function extractReturnVariables(content) {
|
|
110284
110310
|
const lines = content.split("\n");
|
|
110285
110311
|
for (const line2 of lines) {
|
|
@@ -110289,6 +110315,54 @@ function extractReturnVariables(content) {
|
|
|
110289
110315
|
}
|
|
110290
110316
|
return null;
|
|
110291
110317
|
}
|
|
110318
|
+
function buildParsedNamedRequest(requestContent, runtimeVariables, apiDefinitions) {
|
|
110319
|
+
if (apiDefinitions && apiDefinitions.endpoints.length > 0 && isApiRequestLine(requestContent, apiDefinitions.endpoints)) {
|
|
110320
|
+
const apiRequest = parseApiRequest(
|
|
110321
|
+
requestContent,
|
|
110322
|
+
apiDefinitions.endpoints,
|
|
110323
|
+
apiDefinitions.headerGroups
|
|
110324
|
+
);
|
|
110325
|
+
if (apiRequest) {
|
|
110326
|
+
const endpoint = getEndpoint(apiDefinitions, apiRequest.endpointName);
|
|
110327
|
+
if (!endpoint) {
|
|
110328
|
+
throw new Error(`Unknown endpoint: ${apiRequest.endpointName}`);
|
|
110329
|
+
}
|
|
110330
|
+
const resolvedParams = {};
|
|
110331
|
+
for (const [paramName, paramValue] of Object.entries(apiRequest.params)) {
|
|
110332
|
+
if (runtimeVariables[paramValue] !== void 0) {
|
|
110333
|
+
resolvedParams[paramName] = String(runtimeVariables[paramValue]);
|
|
110334
|
+
} else {
|
|
110335
|
+
resolvedParams[paramName] = substituteVariables(paramValue, runtimeVariables);
|
|
110336
|
+
}
|
|
110337
|
+
}
|
|
110338
|
+
const resolvedPath = substituteVariables(
|
|
110339
|
+
resolveEndpointPath(endpoint, resolvedParams),
|
|
110340
|
+
runtimeVariables
|
|
110341
|
+
);
|
|
110342
|
+
const combinedHeaders = {};
|
|
110343
|
+
for (const groupName of apiRequest.headerGroupNames) {
|
|
110344
|
+
const group = getHeaderGroup(apiDefinitions, groupName);
|
|
110345
|
+
if (group) {
|
|
110346
|
+
Object.assign(combinedHeaders, resolveHeaderValues(group, runtimeVariables));
|
|
110347
|
+
}
|
|
110348
|
+
}
|
|
110349
|
+
for (const [headerName, headerValue] of Object.entries(apiRequest.inlineHeaders)) {
|
|
110350
|
+
combinedHeaders[headerName] = substituteVariables(headerValue, runtimeVariables);
|
|
110351
|
+
}
|
|
110352
|
+
return {
|
|
110353
|
+
method: apiRequest.method,
|
|
110354
|
+
url: resolvedPath,
|
|
110355
|
+
headers: combinedHeaders,
|
|
110356
|
+
body: apiRequest.body ? substituteVariables(apiRequest.body, runtimeVariables) : void 0
|
|
110357
|
+
};
|
|
110358
|
+
}
|
|
110359
|
+
}
|
|
110360
|
+
let parsed = parserHttpRequest(requestContent, runtimeVariables);
|
|
110361
|
+
if (apiDefinitions && apiDefinitions.headerGroups.length > 0) {
|
|
110362
|
+
parsed = applyHeaderGroupsToRequest(parsed, requestContent, apiDefinitions.headerGroups, runtimeVariables);
|
|
110363
|
+
}
|
|
110364
|
+
return parsed;
|
|
110365
|
+
}
|
|
110292
110366
|
function parseRunArguments(argsStr) {
|
|
110293
110367
|
const args = [];
|
|
110294
110368
|
if (!argsStr || !argsStr.trim()) {
|
|
@@ -111662,6 +111736,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111662
111736
|
for (let stepIdx = 0; stepIdx < steps.length; stepIdx++) {
|
|
111663
111737
|
const step = steps[stepIdx];
|
|
111664
111738
|
await emitBeforeStep(step, stepIdx);
|
|
111739
|
+
const stepStartTime = Date.now();
|
|
111665
111740
|
try {
|
|
111666
111741
|
if (step.type === "if") {
|
|
111667
111742
|
if (shouldSkip()) {
|
|
@@ -111672,6 +111747,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111672
111747
|
const stepResult = {
|
|
111673
111748
|
type: "print",
|
|
111674
111749
|
stepIndex: stepIdx,
|
|
111750
|
+
durationMs: Date.now() - stepStartTime,
|
|
111675
111751
|
print: {
|
|
111676
111752
|
title: `if ${step.content}: ${conditionMet ? "true" : "false"}`,
|
|
111677
111753
|
timestamp: Date.now() - startTime
|
|
@@ -111713,6 +111789,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111713
111789
|
const stepResult = {
|
|
111714
111790
|
type: "assertion",
|
|
111715
111791
|
stepIndex: stepIdx,
|
|
111792
|
+
durationMs: Date.now() - stepStartTime,
|
|
111716
111793
|
assertion: result,
|
|
111717
111794
|
lineNumber: step.lineNumber
|
|
111718
111795
|
};
|
|
@@ -111721,13 +111798,6 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111721
111798
|
reportProgress(stepIdx, "assertion", `${statusIcon} assert ${result.expression}`, stepResult);
|
|
111722
111799
|
if (!result.passed) {
|
|
111723
111800
|
const failMessage = result.message ? `Assertion failed: ${result.message}` : `Assertion failed: ${result.expression}`;
|
|
111724
|
-
errors.push(failMessage);
|
|
111725
|
-
if (result.error) {
|
|
111726
|
-
errors.push(` Error: ${result.error}`);
|
|
111727
|
-
} else {
|
|
111728
|
-
errors.push(` Expected: ${result.rightExpression ?? result.operator}`);
|
|
111729
|
-
errors.push(` Actual: ${JSON.stringify(result.leftValue)}`);
|
|
111730
|
-
}
|
|
111731
111801
|
await emitFailure(failMessage, step, stepIdx);
|
|
111732
111802
|
return {
|
|
111733
111803
|
name: "",
|
|
@@ -111751,6 +111821,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111751
111821
|
const stepResult = {
|
|
111752
111822
|
type: "print",
|
|
111753
111823
|
stepIndex: stepIdx,
|
|
111824
|
+
durationMs: Date.now() - stepStartTime,
|
|
111754
111825
|
print: {
|
|
111755
111826
|
title,
|
|
111756
111827
|
body,
|
|
@@ -111768,6 +111839,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111768
111839
|
const stepResult = {
|
|
111769
111840
|
type: "print",
|
|
111770
111841
|
stepIndex: stepIdx,
|
|
111842
|
+
durationMs: Date.now() - stepStartTime,
|
|
111771
111843
|
print: {
|
|
111772
111844
|
title: `Waited ${durationMs >= 1e3 ? durationMs / 1e3 + "s" : durationMs + "ms"}`,
|
|
111773
111845
|
timestamp: Date.now() - startTime
|
|
@@ -111796,6 +111868,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111796
111868
|
const stepResult = {
|
|
111797
111869
|
type: "json",
|
|
111798
111870
|
stepIndex: stepIdx,
|
|
111871
|
+
durationMs: Date.now() - stepStartTime,
|
|
111799
111872
|
json: {
|
|
111800
111873
|
varName: parsed.varName,
|
|
111801
111874
|
filePath: result.filePath,
|
|
@@ -112074,6 +112147,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
112074
112147
|
const stepResult = {
|
|
112075
112148
|
type: "sql",
|
|
112076
112149
|
stepIndex: stepIdx,
|
|
112150
|
+
durationMs: Date.now() - stepStartTime,
|
|
112077
112151
|
sql: sqlResult,
|
|
112078
112152
|
variableName: parsed.variableName,
|
|
112079
112153
|
lineNumber: step.lineNumber
|
|
@@ -112479,60 +112553,7 @@ ${indentMultiline(userMessage)}`;
|
|
|
112479
112553
|
let requestUrl = "";
|
|
112480
112554
|
let requestMethod = "";
|
|
112481
112555
|
try {
|
|
112482
|
-
|
|
112483
|
-
if (apiDefinitions && apiDefinitions.endpoints.length > 0 && isApiRequestLine(namedRequest.content, apiDefinitions.endpoints)) {
|
|
112484
|
-
const apiRequest = parseApiRequest(
|
|
112485
|
-
namedRequest.content,
|
|
112486
|
-
apiDefinitions.endpoints,
|
|
112487
|
-
apiDefinitions.headerGroups
|
|
112488
|
-
);
|
|
112489
|
-
if (apiRequest) {
|
|
112490
|
-
const endpoint = getEndpoint(
|
|
112491
|
-
{ headerGroups: apiDefinitions.headerGroups, endpoints: apiDefinitions.endpoints },
|
|
112492
|
-
apiRequest.endpointName
|
|
112493
|
-
);
|
|
112494
|
-
if (endpoint) {
|
|
112495
|
-
let resolvedPath = substituteVariables(endpoint.path, runtimeVariables);
|
|
112496
|
-
for (const [paramName, paramValue] of Object.entries(apiRequest.params)) {
|
|
112497
|
-
let resolvedValue = paramValue;
|
|
112498
|
-
if (runtimeVariables[paramValue] !== void 0) {
|
|
112499
|
-
resolvedValue = String(runtimeVariables[paramValue]);
|
|
112500
|
-
} else {
|
|
112501
|
-
resolvedValue = substituteVariables(paramValue, runtimeVariables);
|
|
112502
|
-
}
|
|
112503
|
-
resolvedPath = resolvedPath.replace(`{${paramName}}`, resolvedValue);
|
|
112504
|
-
}
|
|
112505
|
-
const combinedHeaders = {};
|
|
112506
|
-
for (const groupName of apiRequest.headerGroupNames) {
|
|
112507
|
-
const group = getHeaderGroup(apiDefinitions, groupName);
|
|
112508
|
-
if (group) {
|
|
112509
|
-
const resolvedHeaders = resolveHeaderValues(group, runtimeVariables);
|
|
112510
|
-
Object.assign(combinedHeaders, resolvedHeaders);
|
|
112511
|
-
}
|
|
112512
|
-
}
|
|
112513
|
-
for (const [headerName, headerValue] of Object.entries(apiRequest.inlineHeaders)) {
|
|
112514
|
-
const resolved = substituteVariables(headerValue, runtimeVariables);
|
|
112515
|
-
combinedHeaders[headerName] = resolved;
|
|
112516
|
-
}
|
|
112517
|
-
const bodyParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
|
|
112518
|
-
requestParsed = {
|
|
112519
|
-
method: apiRequest.method,
|
|
112520
|
-
url: resolvedPath,
|
|
112521
|
-
headers: combinedHeaders,
|
|
112522
|
-
body: bodyParsed.body
|
|
112523
|
-
};
|
|
112524
|
-
} else {
|
|
112525
|
-
throw new Error(`Unknown endpoint: ${apiRequest.endpointName}`);
|
|
112526
|
-
}
|
|
112527
|
-
} else {
|
|
112528
|
-
requestParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
|
|
112529
|
-
}
|
|
112530
|
-
} else {
|
|
112531
|
-
requestParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
|
|
112532
|
-
if (apiDefinitions && apiDefinitions.headerGroups.length > 0) {
|
|
112533
|
-
requestParsed = applyHeaderGroupsToRequest(requestParsed, namedRequest.content, apiDefinitions.headerGroups, runtimeVariables);
|
|
112534
|
-
}
|
|
112535
|
-
}
|
|
112556
|
+
const requestParsed = buildParsedNamedRequest(namedRequest.content, runtimeVariables, apiDefinitions);
|
|
112536
112557
|
requestUrl = requestParsed.url;
|
|
112537
112558
|
requestMethod = requestParsed.method;
|
|
112538
112559
|
validatePreparedRequest(
|
|
@@ -112642,7 +112663,7 @@ ${indentMultiline(userMessage)}`;
|
|
|
112642
112663
|
let requestUrl = "";
|
|
112643
112664
|
let requestMethod = "";
|
|
112644
112665
|
try {
|
|
112645
|
-
const requestParsed =
|
|
112666
|
+
const requestParsed = buildParsedNamedRequest(namedRequest.content, runtimeVariables, apiDefinitions);
|
|
112646
112667
|
requestUrl = requestParsed.url;
|
|
112647
112668
|
requestMethod = requestParsed.method;
|
|
112648
112669
|
validatePreparedRequest(
|
|
@@ -112802,12 +112823,7 @@ ${indentMultiline(userMessage)}`;
|
|
|
112802
112823
|
const returnExpressions = extractReturnVariables(targetSequence.content);
|
|
112803
112824
|
if (returnExpressions && returnExpressions.length > 0 && subResult.variables) {
|
|
112804
112825
|
if (returnExpressions.length === 1) {
|
|
112805
|
-
|
|
112806
|
-
if (resolved) {
|
|
112807
|
-
runtimeVariables[varName] = resolved.value;
|
|
112808
|
-
} else {
|
|
112809
|
-
runtimeVariables[varName] = "";
|
|
112810
|
-
}
|
|
112826
|
+
runtimeVariables[varName] = resolveSingleReturnValue(returnExpressions[0], subResult.variables);
|
|
112811
112827
|
} else {
|
|
112812
112828
|
const returnedObj = {};
|
|
112813
112829
|
for (const expr of returnExpressions) {
|
|
@@ -113414,7 +113430,14 @@ function formatSequenceResult(result, options) {
|
|
|
113414
113430
|
lines.push("");
|
|
113415
113431
|
lines.push(colors.warning("Warnings/Errors:"));
|
|
113416
113432
|
for (const err of result.errors) {
|
|
113417
|
-
|
|
113433
|
+
const errLines = String(err ?? "").split("\n");
|
|
113434
|
+
if (errLines.length === 0) {
|
|
113435
|
+
continue;
|
|
113436
|
+
}
|
|
113437
|
+
lines.push(` ${colors.bullet} ${errLines[0]}`);
|
|
113438
|
+
for (const line2 of errLines.slice(1)) {
|
|
113439
|
+
lines.push(` ${line2}`);
|
|
113440
|
+
}
|
|
113418
113441
|
}
|
|
113419
113442
|
}
|
|
113420
113443
|
return lines;
|
|
@@ -113976,7 +113999,7 @@ function generateErrorsHtml(errors, redaction) {
|
|
|
113976
113999
|
<div class="errors">
|
|
113977
114000
|
<h4>Errors</h4>
|
|
113978
114001
|
<ul>
|
|
113979
|
-
${errors.map((e) => `<li>${escapeHtml(redactString(e, redaction))}</li>`).join("\n")}
|
|
114002
|
+
${errors.map((e) => `<li>${escapeHtml(redactString(e, redaction)).replace(/\n/g, "<br>")}</li>`).join("\n")}
|
|
113980
114003
|
</ul>
|
|
113981
114004
|
</div>`;
|
|
113982
114005
|
}
|
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.10.
|
|
5
|
+
"version": "1.10.2",
|
|
6
6
|
"publisher": "Norn-PeterKrustanov",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Peter Krastanov"
|