norn-cli 1.2.1 → 1.2.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 +26 -0
- package/dist/cli.js +131 -17
- package/package.json +1 -1
- package/norn-1.2.0.vsix +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the "Norn" extension will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.2.3] - 2026-02-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Duplicate import detection**: Errors shown when importing files with duplicate definitions
|
|
9
|
+
- Header groups and endpoints from `.nornapi` files
|
|
10
|
+
- Named requests and sequences from `.norn` files
|
|
11
|
+
- Blocks execution in CLI, VS Code, and Test Explorer
|
|
12
|
+
- Red squiggly on import line with descriptive error message
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **.nornenv validation**: `secret var = value` now shows error (use `secret name = value` instead)
|
|
16
|
+
- Syntax highlighting marks invalid pattern in red
|
|
17
|
+
- Diagnostic message explains correct usage
|
|
18
|
+
- **IntelliSense context**: Script types (`bash`, `powershell`, `js`, `readJson`) only appear after `run` keyword
|
|
19
|
+
- **var keyword highlighting**: `var` keyword stays colored while typing variable name
|
|
20
|
+
|
|
21
|
+
## [1.2.2] - 2026-02-05
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- **Coverage for sub-sequences**: Coverage now follows `run SequenceName` calls to track API calls in nested sequences
|
|
25
|
+
- **Coverage for named request blocks**: Named request blocks (`[RequestName]`) with hardcoded URLs are now matched to swagger endpoints by URL path
|
|
26
|
+
- **Auto-refresh coverage on save**: Coverage panel updates automatically when `.norn` or `.nornapi` files are saved
|
|
27
|
+
|
|
28
|
+
### Improved
|
|
29
|
+
- **IntelliSense for .nornapi files**: Content-Type header completions now work in `.nornapi` files (previously only worked in `.norn`)
|
|
30
|
+
|
|
5
31
|
## [1.2.1] - 2026-02-03
|
|
6
32
|
|
|
7
33
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -13493,6 +13493,10 @@ async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__
|
|
|
13493
13493
|
const resolvedPaths = [];
|
|
13494
13494
|
const headerGroups = [];
|
|
13495
13495
|
const endpoints = [];
|
|
13496
|
+
const headerGroupSources = /* @__PURE__ */ new Map();
|
|
13497
|
+
const endpointSources = /* @__PURE__ */ new Map();
|
|
13498
|
+
const namedRequestSources = /* @__PURE__ */ new Map();
|
|
13499
|
+
const sequenceSources = /* @__PURE__ */ new Map();
|
|
13496
13500
|
for (const imp of imports) {
|
|
13497
13501
|
const path4 = await import("path");
|
|
13498
13502
|
const absolutePath = path4.resolve(baseDir, imp.path);
|
|
@@ -13510,16 +13514,64 @@ async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__
|
|
|
13510
13514
|
resolvedPaths.push(absolutePath);
|
|
13511
13515
|
if (imp.path.endsWith(".nornapi")) {
|
|
13512
13516
|
const apiDef = parseNornApiFile(content);
|
|
13513
|
-
|
|
13514
|
-
|
|
13517
|
+
for (const group of apiDef.headerGroups) {
|
|
13518
|
+
const existingSource = headerGroupSources.get(group.name);
|
|
13519
|
+
if (existingSource) {
|
|
13520
|
+
errors.push({
|
|
13521
|
+
path: imp.path,
|
|
13522
|
+
error: `Duplicate header group '${group.name}': already defined in '${existingSource}'`,
|
|
13523
|
+
lineNumber: imp.lineNumber
|
|
13524
|
+
});
|
|
13525
|
+
} else {
|
|
13526
|
+
headerGroupSources.set(group.name, imp.path);
|
|
13527
|
+
headerGroups.push(group);
|
|
13528
|
+
}
|
|
13529
|
+
}
|
|
13530
|
+
for (const endpoint of apiDef.endpoints) {
|
|
13531
|
+
const existingSource = endpointSources.get(endpoint.name);
|
|
13532
|
+
if (existingSource) {
|
|
13533
|
+
errors.push({
|
|
13534
|
+
path: imp.path,
|
|
13535
|
+
error: `Duplicate endpoint '${endpoint.name}': already defined in '${existingSource}'`,
|
|
13536
|
+
lineNumber: imp.lineNumber
|
|
13537
|
+
});
|
|
13538
|
+
} else {
|
|
13539
|
+
endpointSources.set(endpoint.name, imp.path);
|
|
13540
|
+
endpoints.push(endpoint);
|
|
13541
|
+
}
|
|
13542
|
+
}
|
|
13515
13543
|
continue;
|
|
13516
13544
|
}
|
|
13517
13545
|
const importDir = path4.dirname(absolutePath);
|
|
13518
13546
|
const nestedResult = await resolveImports(content, importDir, readFile2, alreadyImported);
|
|
13519
13547
|
errors.push(...nestedResult.errors);
|
|
13520
13548
|
resolvedPaths.push(...nestedResult.resolvedPaths);
|
|
13521
|
-
|
|
13522
|
-
|
|
13549
|
+
for (const group of nestedResult.headerGroups) {
|
|
13550
|
+
const existingSource = headerGroupSources.get(group.name);
|
|
13551
|
+
if (existingSource) {
|
|
13552
|
+
errors.push({
|
|
13553
|
+
path: imp.path,
|
|
13554
|
+
error: `Duplicate header group '${group.name}': already defined in '${existingSource}'`,
|
|
13555
|
+
lineNumber: imp.lineNumber
|
|
13556
|
+
});
|
|
13557
|
+
} else {
|
|
13558
|
+
headerGroupSources.set(group.name, imp.path);
|
|
13559
|
+
headerGroups.push(group);
|
|
13560
|
+
}
|
|
13561
|
+
}
|
|
13562
|
+
for (const endpoint of nestedResult.endpoints) {
|
|
13563
|
+
const existingSource = endpointSources.get(endpoint.name);
|
|
13564
|
+
if (existingSource) {
|
|
13565
|
+
errors.push({
|
|
13566
|
+
path: imp.path,
|
|
13567
|
+
error: `Duplicate endpoint '${endpoint.name}': already defined in '${existingSource}'`,
|
|
13568
|
+
lineNumber: imp.lineNumber
|
|
13569
|
+
});
|
|
13570
|
+
} else {
|
|
13571
|
+
endpointSources.set(endpoint.name, imp.path);
|
|
13572
|
+
endpoints.push(endpoint);
|
|
13573
|
+
}
|
|
13574
|
+
}
|
|
13523
13575
|
if (nestedResult.importedContent) {
|
|
13524
13576
|
importedContents.push(nestedResult.importedContent);
|
|
13525
13577
|
}
|
|
@@ -13527,15 +13579,37 @@ async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__
|
|
|
13527
13579
|
const importedNamedRequests = extractNamedRequests(content);
|
|
13528
13580
|
const importedSequences = extractSequencesFromText(content);
|
|
13529
13581
|
for (const req of importedNamedRequests) {
|
|
13530
|
-
const
|
|
13531
|
-
|
|
13582
|
+
const lowerName = req.name.toLowerCase();
|
|
13583
|
+
const existingSource = namedRequestSources.get(lowerName);
|
|
13584
|
+
if (existingSource) {
|
|
13585
|
+
errors.push({
|
|
13586
|
+
path: imp.path,
|
|
13587
|
+
error: `Duplicate named request '${req.name}': already defined in '${existingSource}'`,
|
|
13588
|
+
lineNumber: imp.lineNumber
|
|
13589
|
+
});
|
|
13590
|
+
} else {
|
|
13591
|
+
namedRequestSources.set(lowerName, imp.path);
|
|
13592
|
+
const resolvedContent = substituteVariables(req.content, importedVariables);
|
|
13593
|
+
importedContents.push(`[${req.name}]
|
|
13532
13594
|
${resolvedContent}`);
|
|
13595
|
+
}
|
|
13533
13596
|
}
|
|
13534
13597
|
for (const seq of importedSequences) {
|
|
13535
|
-
const
|
|
13536
|
-
|
|
13598
|
+
const lowerName = seq.name.toLowerCase();
|
|
13599
|
+
const existingSource = sequenceSources.get(lowerName);
|
|
13600
|
+
if (existingSource) {
|
|
13601
|
+
errors.push({
|
|
13602
|
+
path: imp.path,
|
|
13603
|
+
error: `Duplicate sequence '${seq.name}': already defined in '${existingSource}'`,
|
|
13604
|
+
lineNumber: imp.lineNumber
|
|
13605
|
+
});
|
|
13606
|
+
} else {
|
|
13607
|
+
sequenceSources.set(lowerName, imp.path);
|
|
13608
|
+
const resolvedContent = substituteVariables(seq.content, importedVariables);
|
|
13609
|
+
importedContents.push(`sequence ${seq.name}
|
|
13537
13610
|
${resolvedContent}
|
|
13538
13611
|
end sequence`);
|
|
13612
|
+
}
|
|
13539
13613
|
}
|
|
13540
13614
|
} catch (error) {
|
|
13541
13615
|
errors.push({
|
|
@@ -21228,14 +21302,28 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
21228
21302
|
if (endpoint && apiDefinitions) {
|
|
21229
21303
|
const paramsStr = endpointMatch[2] || "";
|
|
21230
21304
|
const headerGroupsStr = endpointMatch[3]?.trim() || "";
|
|
21231
|
-
const
|
|
21305
|
+
const paramTokens = paramsStr ? paramsStr.split(",").map((p) => p.trim()) : [];
|
|
21232
21306
|
const params = {};
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21236
|
-
|
|
21307
|
+
const isNamedSyntax = paramTokens.length > 0 && paramTokens[0].includes(":");
|
|
21308
|
+
if (isNamedSyntax) {
|
|
21309
|
+
for (const token of paramTokens) {
|
|
21310
|
+
const kvMatch = token.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.+)$/);
|
|
21311
|
+
if (kvMatch) {
|
|
21312
|
+
const paramName = kvMatch[1];
|
|
21313
|
+
let paramValue = kvMatch[2].trim().replace(/^["']|["']$/g, "");
|
|
21314
|
+
const bareResolved = resolveBareVariables(paramValue, runtimeVariables);
|
|
21315
|
+
params[paramName] = substituteVariables(bareResolved, runtimeVariables);
|
|
21316
|
+
}
|
|
21237
21317
|
}
|
|
21238
|
-
}
|
|
21318
|
+
} else {
|
|
21319
|
+
endpoint.parameters.forEach((paramName, idx) => {
|
|
21320
|
+
if (paramTokens[idx] !== void 0) {
|
|
21321
|
+
let paramValue = paramTokens[idx].replace(/^["']|["']$/g, "");
|
|
21322
|
+
const bareResolved = resolveBareVariables(paramValue, runtimeVariables);
|
|
21323
|
+
params[paramName] = substituteVariables(bareResolved, runtimeVariables);
|
|
21324
|
+
}
|
|
21325
|
+
});
|
|
21326
|
+
}
|
|
21239
21327
|
let pathWithVars = substituteVariables(endpoint.path, runtimeVariables);
|
|
21240
21328
|
for (const paramName of endpoint.parameters) {
|
|
21241
21329
|
const value = params[paramName];
|
|
@@ -21257,7 +21345,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
21257
21345
|
}
|
|
21258
21346
|
}
|
|
21259
21347
|
}
|
|
21260
|
-
requestDescription = `${potentialEndpointName}(${
|
|
21348
|
+
requestDescription = `${potentialEndpointName}(${paramTokens.join(", ")}) \u2192 ${parsed.method} ${resolvedUrl}`;
|
|
21261
21349
|
} else {
|
|
21262
21350
|
resolvedUrl = substituteVariables(parsed.url, runtimeVariables);
|
|
21263
21351
|
requestDescription = `${parsed.method} ${resolvedUrl}`;
|
|
@@ -23389,9 +23477,22 @@ async function main() {
|
|
|
23389
23477
|
workingDir,
|
|
23390
23478
|
async (importPath) => fsPromises.readFile(importPath, "utf-8")
|
|
23391
23479
|
);
|
|
23392
|
-
|
|
23480
|
+
const duplicateErrors = importResult.errors.filter(
|
|
23481
|
+
(err) => err.error.includes("Duplicate header group") || err.error.includes("Duplicate endpoint") || err.error.includes("Duplicate named request") || err.error.includes("Duplicate sequence")
|
|
23482
|
+
);
|
|
23483
|
+
const otherErrors = importResult.errors.filter(
|
|
23484
|
+
(err) => !err.error.includes("Duplicate header group") && !err.error.includes("Duplicate endpoint") && !err.error.includes("Duplicate named request") && !err.error.includes("Duplicate sequence")
|
|
23485
|
+
);
|
|
23486
|
+
for (const err of otherErrors) {
|
|
23393
23487
|
console.error(colors.warning(`Import warning: ${err.path} - ${err.error}`));
|
|
23394
23488
|
}
|
|
23489
|
+
if (duplicateErrors.length > 0) {
|
|
23490
|
+
console.error(colors.error("Cannot execute - duplicate definitions found:"));
|
|
23491
|
+
for (const err of duplicateErrors) {
|
|
23492
|
+
console.error(colors.error(` ${err.path}: ${err.error}`));
|
|
23493
|
+
}
|
|
23494
|
+
process.exit(1);
|
|
23495
|
+
}
|
|
23395
23496
|
const fileContentWithImports = importResult.importedContent ? `${importResult.importedContent}
|
|
23396
23497
|
|
|
23397
23498
|
${fileContent}` : fileContent;
|
|
@@ -23498,9 +23599,22 @@ ${fileContent}` : fileContent;
|
|
|
23498
23599
|
workingDir,
|
|
23499
23600
|
async (importPath) => fsPromises.readFile(importPath, "utf-8")
|
|
23500
23601
|
);
|
|
23501
|
-
|
|
23602
|
+
const duplicateErrors2 = importResult.errors.filter(
|
|
23603
|
+
(err) => err.error.includes("Duplicate header group") || err.error.includes("Duplicate endpoint") || err.error.includes("Duplicate named request") || err.error.includes("Duplicate sequence")
|
|
23604
|
+
);
|
|
23605
|
+
const otherErrors2 = importResult.errors.filter(
|
|
23606
|
+
(err) => !err.error.includes("Duplicate header group") && !err.error.includes("Duplicate endpoint") && !err.error.includes("Duplicate named request") && !err.error.includes("Duplicate sequence")
|
|
23607
|
+
);
|
|
23608
|
+
for (const err of otherErrors2) {
|
|
23502
23609
|
console.error(colors.warning(`Import warning: ${err.path} - ${err.error}`));
|
|
23503
23610
|
}
|
|
23611
|
+
if (duplicateErrors2.length > 0) {
|
|
23612
|
+
console.error(colors.error("Cannot execute - duplicate definitions found:"));
|
|
23613
|
+
for (const err of duplicateErrors2) {
|
|
23614
|
+
console.error(colors.error(` ${err.path}: ${err.error}`));
|
|
23615
|
+
}
|
|
23616
|
+
process.exit(1);
|
|
23617
|
+
}
|
|
23504
23618
|
const fileContentWithImports = importResult.importedContent ? `${importResult.importedContent}
|
|
23505
23619
|
|
|
23506
23620
|
${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.
|
|
5
|
+
"version": "1.2.3",
|
|
6
6
|
"publisher": "Norn-PeterKrustanov",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Peter Krastanov"
|
package/norn-1.2.0.vsix
DELETED
|
Binary file
|