norn-cli 1.10.1 → 1.10.3
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 +194 -82
- 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 = [];
|
|
@@ -109438,6 +109460,9 @@ function getConnectionValue(values, ...keys) {
|
|
|
109438
109460
|
function getConnectionString(values) {
|
|
109439
109461
|
return getConnectionValue(values, "connectionString");
|
|
109440
109462
|
}
|
|
109463
|
+
function usesWindowsIntegratedSqlServerAuth(connectionString) {
|
|
109464
|
+
return /(?:^|;)\s*Integrated\s+Security\s*=\s*(?:true|yes|sspi)\s*(?:;|$)/i.test(connectionString) || /(?:^|;)\s*Trusted_Connection\s*=\s*(?:true|yes|sspi)\s*(?:;|$)/i.test(connectionString);
|
|
109465
|
+
}
|
|
109441
109466
|
function buildMissingConnectionError(adapterLabel, profile) {
|
|
109442
109467
|
return `Missing required ${adapterLabel} connection string. Expected 'connectionString ${profile} = ...' in .nornenv.`;
|
|
109443
109468
|
}
|
|
@@ -109495,7 +109520,7 @@ if ([string]::IsNullOrWhiteSpace($raw)) {
|
|
|
109495
109520
|
throw 'Missing SQL adapter payload.'
|
|
109496
109521
|
}
|
|
109497
109522
|
|
|
109498
|
-
$payload = $raw | ConvertFrom-Json
|
|
109523
|
+
$payload = $raw | ConvertFrom-Json
|
|
109499
109524
|
$connectionString = [string]$payload.connection.values.connectionString
|
|
109500
109525
|
if ([string]::IsNullOrWhiteSpace($connectionString)) {
|
|
109501
109526
|
throw 'Missing SQL Server Integrated Auth connection string.'
|
|
@@ -109526,9 +109551,13 @@ $resolveHandler = [System.ResolveEventHandler]{
|
|
|
109526
109551
|
$sqlText = [string]$payload.operation.sql
|
|
109527
109552
|
$mode = [string]$payload.mode
|
|
109528
109553
|
$paramsObject = $payload.params
|
|
109554
|
+
$connectionStringHasUserId = $connectionString -match '(?i)(?:^|;)s*(?:User ID|UID)s*='
|
|
109555
|
+
$connectionStringHasPassword = $connectionString -match '(?i)(?:^|;)s*(?:Password|PWD)s*='
|
|
109529
109556
|
|
|
109530
109557
|
$connection = $null
|
|
109558
|
+
$connectionBuilder = $null
|
|
109531
109559
|
try {
|
|
109560
|
+
$connectionBuilder = New-Object Microsoft.Data.SqlClient.SqlConnectionStringBuilder $connectionString
|
|
109532
109561
|
$connection = New-Object Microsoft.Data.SqlClient.SqlConnection $connectionString
|
|
109533
109562
|
$command = $connection.CreateCommand()
|
|
109534
109563
|
$command.CommandText = $sqlText
|
|
@@ -109600,18 +109629,97 @@ try {
|
|
|
109600
109629
|
$response | ConvertTo-Json -Depth 100 -Compress
|
|
109601
109630
|
}
|
|
109602
109631
|
catch {
|
|
109603
|
-
$details =
|
|
109632
|
+
$details = New-Object System.Collections.Generic.List[string]
|
|
109604
109633
|
if ($_.Exception -and $_.Exception.GetType()) {
|
|
109605
|
-
$details
|
|
109634
|
+
$details.Add("type: $($_.Exception.GetType().FullName)")
|
|
109606
109635
|
}
|
|
109607
109636
|
if ($_.InvocationInfo -and $_.InvocationInfo.ScriptLineNumber) {
|
|
109608
|
-
$details
|
|
109637
|
+
$details.Add("line: $($_.InvocationInfo.ScriptLineNumber)")
|
|
109609
109638
|
}
|
|
109610
109639
|
if ($_.FullyQualifiedErrorId) {
|
|
109611
|
-
$details
|
|
109640
|
+
$details.Add("id: $($_.FullyQualifiedErrorId)")
|
|
109641
|
+
}
|
|
109642
|
+
|
|
109643
|
+
try {
|
|
109644
|
+
$details.Add("adapter: sqlserver-windows")
|
|
109645
|
+
$details.Add("powershell: $($PSVersionTable.PSVersion)")
|
|
109646
|
+
$details.Add("sqlClientVersion: $(([Microsoft.Data.SqlClient.SqlConnection]).Assembly.GetName().Version)")
|
|
109647
|
+
$details.Add("windowsIdentity: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)")
|
|
109648
|
+
} catch {
|
|
109649
|
+
}
|
|
109650
|
+
|
|
109651
|
+
if ($null -ne $connectionBuilder) {
|
|
109652
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$connectionBuilder.DataSource)) {
|
|
109653
|
+
$details.Add("dataSource: $($connectionBuilder.DataSource)")
|
|
109654
|
+
}
|
|
109655
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$connectionBuilder.InitialCatalog)) {
|
|
109656
|
+
$details.Add("database: $($connectionBuilder.InitialCatalog)")
|
|
109657
|
+
}
|
|
109658
|
+
$details.Add("integratedSecurity: $($connectionBuilder.IntegratedSecurity)")
|
|
109659
|
+
$details.Add("encrypt: $($connectionBuilder.Encrypt)")
|
|
109660
|
+
$details.Add("trustServerCertificate: $($connectionBuilder.TrustServerCertificate)")
|
|
109661
|
+
$details.Add("applicationIntent: $($connectionBuilder.ApplicationIntent)")
|
|
109662
|
+
$details.Add("multiSubnetFailover: $($connectionBuilder.MultiSubnetFailover)")
|
|
109663
|
+
|
|
109664
|
+
try {
|
|
109665
|
+
$hostNameInCertificate = [string]$connectionBuilder['HostNameInCertificate']
|
|
109666
|
+
if (-not [string]::IsNullOrWhiteSpace($hostNameInCertificate)) {
|
|
109667
|
+
$details.Add("hostNameInCertificate: $hostNameInCertificate")
|
|
109668
|
+
}
|
|
109669
|
+
} catch {
|
|
109670
|
+
}
|
|
109671
|
+
}
|
|
109672
|
+
|
|
109673
|
+
$details.Add("userIdPresent: $connectionStringHasUserId")
|
|
109674
|
+
$details.Add("passwordPresent: $connectionStringHasPassword")
|
|
109675
|
+
|
|
109676
|
+
$sqlException = $null
|
|
109677
|
+
if ($_.Exception -is [Microsoft.Data.SqlClient.SqlException]) {
|
|
109678
|
+
$sqlException = [Microsoft.Data.SqlClient.SqlException]$_.Exception
|
|
109679
|
+
} elseif ($_.Exception -and $_.Exception.InnerException -is [Microsoft.Data.SqlClient.SqlException]) {
|
|
109680
|
+
$sqlException = [Microsoft.Data.SqlClient.SqlException]$_.Exception.InnerException
|
|
109681
|
+
}
|
|
109682
|
+
|
|
109683
|
+
if ($null -ne $sqlException) {
|
|
109684
|
+
$details.Add("sqlNumber: $($sqlException.Number)")
|
|
109685
|
+
$details.Add("sqlState: $($sqlException.State)")
|
|
109686
|
+
$details.Add("sqlClass: $($sqlException.Class)")
|
|
109687
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$sqlException.Server)) {
|
|
109688
|
+
$details.Add("sqlServer: $($sqlException.Server)")
|
|
109689
|
+
}
|
|
109690
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$sqlException.Procedure)) {
|
|
109691
|
+
$details.Add("sqlProcedure: $($sqlException.Procedure)")
|
|
109692
|
+
}
|
|
109693
|
+
if ($sqlException.LineNumber -gt 0) {
|
|
109694
|
+
$details.Add("sqlLineNumber: $($sqlException.LineNumber)")
|
|
109695
|
+
}
|
|
109696
|
+
if ($sqlException.ClientConnectionId -ne [guid]::Empty) {
|
|
109697
|
+
$details.Add("clientConnectionId: $($sqlException.ClientConnectionId)")
|
|
109698
|
+
}
|
|
109699
|
+
if ($sqlException.Errors -and $sqlException.Errors.Count -gt 0) {
|
|
109700
|
+
$messages = @()
|
|
109701
|
+
foreach ($sqlError in $sqlException.Errors) {
|
|
109702
|
+
if ($sqlError -and -not [string]::IsNullOrWhiteSpace([string]$sqlError.Message)) {
|
|
109703
|
+
$messages += [string]$sqlError.Message
|
|
109704
|
+
}
|
|
109705
|
+
}
|
|
109706
|
+
if ($messages.Count -gt 0) {
|
|
109707
|
+
$details.Add("sqlErrors: $($messages -join ' | ')")
|
|
109708
|
+
}
|
|
109709
|
+
}
|
|
109710
|
+
}
|
|
109711
|
+
|
|
109712
|
+
if ($_.Exception -and $_.Exception.InnerException) {
|
|
109713
|
+
$details.Add("innerType: $($_.Exception.InnerException.GetType().FullName)")
|
|
109714
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$_.Exception.InnerException.Message)) {
|
|
109715
|
+
$details.Add("innerMessage: $($_.Exception.InnerException.Message)")
|
|
109716
|
+
}
|
|
109717
|
+
}
|
|
109718
|
+
|
|
109719
|
+
[Console]::Error.WriteLine("$($_.Exception.Message)")
|
|
109720
|
+
foreach ($detail in $details) {
|
|
109721
|
+
[Console]::Error.WriteLine(" $detail")
|
|
109612
109722
|
}
|
|
109613
|
-
$suffix = if ($details.Count -gt 0) { " ($($details -join ', '))" } else { '' }
|
|
109614
|
-
[Console]::Error.WriteLine("$($_.Exception.Message)$suffix")
|
|
109615
109723
|
exit 1
|
|
109616
109724
|
}
|
|
109617
109725
|
finally {
|
|
@@ -109772,6 +109880,9 @@ async function runBuiltInSqlServerAdapter(request) {
|
|
|
109772
109880
|
if (!connectionString) {
|
|
109773
109881
|
fail(buildMissingConnectionError("SQL Server", request.connection.profile));
|
|
109774
109882
|
}
|
|
109883
|
+
if (usesWindowsIntegratedSqlServerAuth(connectionString)) {
|
|
109884
|
+
fail("This SQL Server connection string uses Windows/Integrated authentication. Use adapter 'sqlserver-windows' in norn.sql.json.");
|
|
109885
|
+
}
|
|
109775
109886
|
const pool = new mssql.ConnectionPool(connectionString);
|
|
109776
109887
|
try {
|
|
109777
109888
|
await pool.connect();
|
|
@@ -109810,7 +109921,7 @@ async function runBuiltInSqlServerIntegratedAdapter(request) {
|
|
|
109810
109921
|
const values = request.connection.values;
|
|
109811
109922
|
const connectionString = getConnectionString(values);
|
|
109812
109923
|
if (!connectionString) {
|
|
109813
|
-
fail(buildMissingConnectionError("SQL Server
|
|
109924
|
+
fail(buildMissingConnectionError("SQL Server (Windows)", request.connection.profile));
|
|
109814
109925
|
}
|
|
109815
109926
|
const compiled = compileSqlServerSql(request.operation.sql, request.params || {});
|
|
109816
109927
|
const compiledRequest = {
|
|
@@ -109824,7 +109935,7 @@ async function runBuiltInSqlServerIntegratedAdapter(request) {
|
|
|
109824
109935
|
try {
|
|
109825
109936
|
return await runSqlServerIntegratedViaPowerShell(compiledRequest);
|
|
109826
109937
|
} catch (error) {
|
|
109827
|
-
throw formatBuiltInDriverError("SQL Server
|
|
109938
|
+
throw formatBuiltInDriverError("SQL Server (Windows)", error);
|
|
109828
109939
|
}
|
|
109829
109940
|
}
|
|
109830
109941
|
function isBuiltInSqlAdapter(adapterId) {
|
|
@@ -110280,6 +110391,10 @@ function resolveReturnExpression(expr, runtimeVariables) {
|
|
|
110280
110391
|
return { key: varName, value: varValue };
|
|
110281
110392
|
}
|
|
110282
110393
|
}
|
|
110394
|
+
function resolveSingleReturnValue(expr, runtimeVariables) {
|
|
110395
|
+
const result = evaluateSqlArgumentExpression(expr, runtimeVariables);
|
|
110396
|
+
return result.error ? "" : result.value;
|
|
110397
|
+
}
|
|
110283
110398
|
function extractReturnVariables(content) {
|
|
110284
110399
|
const lines = content.split("\n");
|
|
110285
110400
|
for (const line2 of lines) {
|
|
@@ -110289,6 +110404,54 @@ function extractReturnVariables(content) {
|
|
|
110289
110404
|
}
|
|
110290
110405
|
return null;
|
|
110291
110406
|
}
|
|
110407
|
+
function buildParsedNamedRequest(requestContent, runtimeVariables, apiDefinitions) {
|
|
110408
|
+
if (apiDefinitions && apiDefinitions.endpoints.length > 0 && isApiRequestLine(requestContent, apiDefinitions.endpoints)) {
|
|
110409
|
+
const apiRequest = parseApiRequest(
|
|
110410
|
+
requestContent,
|
|
110411
|
+
apiDefinitions.endpoints,
|
|
110412
|
+
apiDefinitions.headerGroups
|
|
110413
|
+
);
|
|
110414
|
+
if (apiRequest) {
|
|
110415
|
+
const endpoint = getEndpoint(apiDefinitions, apiRequest.endpointName);
|
|
110416
|
+
if (!endpoint) {
|
|
110417
|
+
throw new Error(`Unknown endpoint: ${apiRequest.endpointName}`);
|
|
110418
|
+
}
|
|
110419
|
+
const resolvedParams = {};
|
|
110420
|
+
for (const [paramName, paramValue] of Object.entries(apiRequest.params)) {
|
|
110421
|
+
if (runtimeVariables[paramValue] !== void 0) {
|
|
110422
|
+
resolvedParams[paramName] = String(runtimeVariables[paramValue]);
|
|
110423
|
+
} else {
|
|
110424
|
+
resolvedParams[paramName] = substituteVariables(paramValue, runtimeVariables);
|
|
110425
|
+
}
|
|
110426
|
+
}
|
|
110427
|
+
const resolvedPath = substituteVariables(
|
|
110428
|
+
resolveEndpointPath(endpoint, resolvedParams),
|
|
110429
|
+
runtimeVariables
|
|
110430
|
+
);
|
|
110431
|
+
const combinedHeaders = {};
|
|
110432
|
+
for (const groupName of apiRequest.headerGroupNames) {
|
|
110433
|
+
const group = getHeaderGroup(apiDefinitions, groupName);
|
|
110434
|
+
if (group) {
|
|
110435
|
+
Object.assign(combinedHeaders, resolveHeaderValues(group, runtimeVariables));
|
|
110436
|
+
}
|
|
110437
|
+
}
|
|
110438
|
+
for (const [headerName, headerValue] of Object.entries(apiRequest.inlineHeaders)) {
|
|
110439
|
+
combinedHeaders[headerName] = substituteVariables(headerValue, runtimeVariables);
|
|
110440
|
+
}
|
|
110441
|
+
return {
|
|
110442
|
+
method: apiRequest.method,
|
|
110443
|
+
url: resolvedPath,
|
|
110444
|
+
headers: combinedHeaders,
|
|
110445
|
+
body: apiRequest.body ? substituteVariables(apiRequest.body, runtimeVariables) : void 0
|
|
110446
|
+
};
|
|
110447
|
+
}
|
|
110448
|
+
}
|
|
110449
|
+
let parsed = parserHttpRequest(requestContent, runtimeVariables);
|
|
110450
|
+
if (apiDefinitions && apiDefinitions.headerGroups.length > 0) {
|
|
110451
|
+
parsed = applyHeaderGroupsToRequest(parsed, requestContent, apiDefinitions.headerGroups, runtimeVariables);
|
|
110452
|
+
}
|
|
110453
|
+
return parsed;
|
|
110454
|
+
}
|
|
110292
110455
|
function parseRunArguments(argsStr) {
|
|
110293
110456
|
const args = [];
|
|
110294
110457
|
if (!argsStr || !argsStr.trim()) {
|
|
@@ -111662,6 +111825,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111662
111825
|
for (let stepIdx = 0; stepIdx < steps.length; stepIdx++) {
|
|
111663
111826
|
const step = steps[stepIdx];
|
|
111664
111827
|
await emitBeforeStep(step, stepIdx);
|
|
111828
|
+
const stepStartTime = Date.now();
|
|
111665
111829
|
try {
|
|
111666
111830
|
if (step.type === "if") {
|
|
111667
111831
|
if (shouldSkip()) {
|
|
@@ -111672,6 +111836,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111672
111836
|
const stepResult = {
|
|
111673
111837
|
type: "print",
|
|
111674
111838
|
stepIndex: stepIdx,
|
|
111839
|
+
durationMs: Date.now() - stepStartTime,
|
|
111675
111840
|
print: {
|
|
111676
111841
|
title: `if ${step.content}: ${conditionMet ? "true" : "false"}`,
|
|
111677
111842
|
timestamp: Date.now() - startTime
|
|
@@ -111713,6 +111878,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111713
111878
|
const stepResult = {
|
|
111714
111879
|
type: "assertion",
|
|
111715
111880
|
stepIndex: stepIdx,
|
|
111881
|
+
durationMs: Date.now() - stepStartTime,
|
|
111716
111882
|
assertion: result,
|
|
111717
111883
|
lineNumber: step.lineNumber
|
|
111718
111884
|
};
|
|
@@ -111721,13 +111887,6 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111721
111887
|
reportProgress(stepIdx, "assertion", `${statusIcon} assert ${result.expression}`, stepResult);
|
|
111722
111888
|
if (!result.passed) {
|
|
111723
111889
|
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
111890
|
await emitFailure(failMessage, step, stepIdx);
|
|
111732
111891
|
return {
|
|
111733
111892
|
name: "",
|
|
@@ -111751,6 +111910,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111751
111910
|
const stepResult = {
|
|
111752
111911
|
type: "print",
|
|
111753
111912
|
stepIndex: stepIdx,
|
|
111913
|
+
durationMs: Date.now() - stepStartTime,
|
|
111754
111914
|
print: {
|
|
111755
111915
|
title,
|
|
111756
111916
|
body,
|
|
@@ -111768,6 +111928,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111768
111928
|
const stepResult = {
|
|
111769
111929
|
type: "print",
|
|
111770
111930
|
stepIndex: stepIdx,
|
|
111931
|
+
durationMs: Date.now() - stepStartTime,
|
|
111771
111932
|
print: {
|
|
111772
111933
|
title: `Waited ${durationMs >= 1e3 ? durationMs / 1e3 + "s" : durationMs + "ms"}`,
|
|
111773
111934
|
timestamp: Date.now() - startTime
|
|
@@ -111796,6 +111957,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
111796
111957
|
const stepResult = {
|
|
111797
111958
|
type: "json",
|
|
111798
111959
|
stepIndex: stepIdx,
|
|
111960
|
+
durationMs: Date.now() - stepStartTime,
|
|
111799
111961
|
json: {
|
|
111800
111962
|
varName: parsed.varName,
|
|
111801
111963
|
filePath: result.filePath,
|
|
@@ -112074,6 +112236,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
112074
112236
|
const stepResult = {
|
|
112075
112237
|
type: "sql",
|
|
112076
112238
|
stepIndex: stepIdx,
|
|
112239
|
+
durationMs: Date.now() - stepStartTime,
|
|
112077
112240
|
sql: sqlResult,
|
|
112078
112241
|
variableName: parsed.variableName,
|
|
112079
112242
|
lineNumber: step.lineNumber
|
|
@@ -112479,60 +112642,7 @@ ${indentMultiline(userMessage)}`;
|
|
|
112479
112642
|
let requestUrl = "";
|
|
112480
112643
|
let requestMethod = "";
|
|
112481
112644
|
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
|
-
}
|
|
112645
|
+
const requestParsed = buildParsedNamedRequest(namedRequest.content, runtimeVariables, apiDefinitions);
|
|
112536
112646
|
requestUrl = requestParsed.url;
|
|
112537
112647
|
requestMethod = requestParsed.method;
|
|
112538
112648
|
validatePreparedRequest(
|
|
@@ -112642,7 +112752,7 @@ ${indentMultiline(userMessage)}`;
|
|
|
112642
112752
|
let requestUrl = "";
|
|
112643
112753
|
let requestMethod = "";
|
|
112644
112754
|
try {
|
|
112645
|
-
const requestParsed =
|
|
112755
|
+
const requestParsed = buildParsedNamedRequest(namedRequest.content, runtimeVariables, apiDefinitions);
|
|
112646
112756
|
requestUrl = requestParsed.url;
|
|
112647
112757
|
requestMethod = requestParsed.method;
|
|
112648
112758
|
validatePreparedRequest(
|
|
@@ -112802,12 +112912,7 @@ ${indentMultiline(userMessage)}`;
|
|
|
112802
112912
|
const returnExpressions = extractReturnVariables(targetSequence.content);
|
|
112803
112913
|
if (returnExpressions && returnExpressions.length > 0 && subResult.variables) {
|
|
112804
112914
|
if (returnExpressions.length === 1) {
|
|
112805
|
-
|
|
112806
|
-
if (resolved) {
|
|
112807
|
-
runtimeVariables[varName] = resolved.value;
|
|
112808
|
-
} else {
|
|
112809
|
-
runtimeVariables[varName] = "";
|
|
112810
|
-
}
|
|
112915
|
+
runtimeVariables[varName] = resolveSingleReturnValue(returnExpressions[0], subResult.variables);
|
|
112811
112916
|
} else {
|
|
112812
112917
|
const returnedObj = {};
|
|
112813
112918
|
for (const expr of returnExpressions) {
|
|
@@ -113414,7 +113519,14 @@ function formatSequenceResult(result, options) {
|
|
|
113414
113519
|
lines.push("");
|
|
113415
113520
|
lines.push(colors.warning("Warnings/Errors:"));
|
|
113416
113521
|
for (const err of result.errors) {
|
|
113417
|
-
|
|
113522
|
+
const errLines = String(err ?? "").split("\n");
|
|
113523
|
+
if (errLines.length === 0) {
|
|
113524
|
+
continue;
|
|
113525
|
+
}
|
|
113526
|
+
lines.push(` ${colors.bullet} ${errLines[0]}`);
|
|
113527
|
+
for (const line2 of errLines.slice(1)) {
|
|
113528
|
+
lines.push(` ${line2}`);
|
|
113529
|
+
}
|
|
113418
113530
|
}
|
|
113419
113531
|
}
|
|
113420
113532
|
return lines;
|
|
@@ -113976,7 +114088,7 @@ function generateErrorsHtml(errors, redaction) {
|
|
|
113976
114088
|
<div class="errors">
|
|
113977
114089
|
<h4>Errors</h4>
|
|
113978
114090
|
<ul>
|
|
113979
|
-
${errors.map((e) => `<li>${escapeHtml(redactString(e, redaction))}</li>`).join("\n")}
|
|
114091
|
+
${errors.map((e) => `<li>${escapeHtml(redactString(e, redaction)).replace(/\n/g, "<br>")}</li>`).join("\n")}
|
|
113980
114092
|
</ul>
|
|
113981
114093
|
</div>`;
|
|
113982
114094
|
}
|
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.3",
|
|
6
6
|
"publisher": "Norn-PeterKrustanov",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Peter Krastanov"
|