norn-cli 1.3.16 → 1.3.18
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/AGENTS.md +72 -0
- package/CHANGELOG.md +34 -1
- package/README.md +4 -2
- package/dist/cli.js +135 -63
- package/out/assertionRunner.js +537 -0
- package/out/cli/colors.js +129 -0
- package/out/cli/formatters/assertion.js +75 -0
- package/out/cli/formatters/index.js +23 -0
- package/out/cli/formatters/response.js +106 -0
- package/out/cli/formatters/summary.js +187 -0
- package/out/cli/redaction.js +237 -0
- package/out/cli/reporters/html.js +634 -0
- package/out/cli/reporters/index.js +22 -0
- package/out/cli/reporters/junit.js +211 -0
- package/out/cli.js +926 -0
- package/out/codeLensProvider.js +254 -0
- package/out/compareContentProvider.js +85 -0
- package/out/completionProvider.js +1886 -0
- package/out/contractDecorationProvider.js +243 -0
- package/out/coverageCalculator.js +756 -0
- package/out/coveragePanel.js +542 -0
- package/out/diagnosticProvider.js +980 -0
- package/out/environmentProvider.js +373 -0
- package/out/extension.js +1025 -0
- package/out/httpClient.js +269 -0
- package/out/jsonFileReader.js +320 -0
- package/out/nornapiParser.js +326 -0
- package/out/parser.js +725 -0
- package/out/responsePanel.js +4674 -0
- package/out/schemaGenerator.js +393 -0
- package/out/scriptRunner.js +419 -0
- package/out/sequenceRunner.js +3046 -0
- package/out/swaggerParser.js +339 -0
- package/out/test/extension.test.js +48 -0
- package/out/testProvider.js +658 -0
- package/out/validationCache.js +245 -0
- package/package.json +1 -1
package/AGENTS.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Norn Extension - Copilot Instructions
|
|
2
|
+
|
|
3
|
+
These instructions apply to all conversations about the Norn VS Code extension.
|
|
4
|
+
|
|
5
|
+
## CLI Support is Mandatory
|
|
6
|
+
|
|
7
|
+
Every feature implementation must work in both:
|
|
8
|
+
1. **VS Code Extension** - Interactive UI with response panel
|
|
9
|
+
2. **CLI** (`src/cli.ts`) - Command-line execution
|
|
10
|
+
|
|
11
|
+
Before considering any feature complete, verify it works in the CLI. The CLI shares code with the extension (`parser.ts`, `sequenceRunner.ts`, `assertionRunner.ts`, `httpClient.ts`).
|
|
12
|
+
|
|
13
|
+
### CRITICAL: Running the Local CLI
|
|
14
|
+
|
|
15
|
+
**NEVER use `npx norn`** - this runs the globally installed npm package, NOT your local changes!
|
|
16
|
+
|
|
17
|
+
**ALWAYS use the local compiled CLI:**
|
|
18
|
+
```bash
|
|
19
|
+
# Correct - runs your local development version
|
|
20
|
+
node ./dist/cli.js run tests/file.norn --env prelive
|
|
21
|
+
|
|
22
|
+
# WRONG - runs the published npm package, ignores your changes
|
|
23
|
+
npx norn run tests/file.norn
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Before testing CLI changes:
|
|
27
|
+
1. Run `npm run compile` to build the latest code
|
|
28
|
+
2. Use `node ./dist/cli.js` to execute
|
|
29
|
+
|
|
30
|
+
## Skills Maintenance
|
|
31
|
+
|
|
32
|
+
When implementing features, check the `.github/skills/` directory for relevant skills:
|
|
33
|
+
|
|
34
|
+
- **If a skill is incorrect or outdated:** Update it with the correct information
|
|
35
|
+
- **If a skill is missing:** Create a new one following the Agent Skills format
|
|
36
|
+
|
|
37
|
+
Skills should capture lessons learned and patterns discovered during implementation to help future development.
|
|
38
|
+
|
|
39
|
+
## Code Style
|
|
40
|
+
|
|
41
|
+
- TypeScript with strict typing
|
|
42
|
+
- Simple IntelliSense completions (no complex snippets)
|
|
43
|
+
- Keep parsing logic in dedicated functions
|
|
44
|
+
- Test both extension and CLI after changes
|
|
45
|
+
|
|
46
|
+
## Test Verification
|
|
47
|
+
|
|
48
|
+
**MANDATORY:** When the user asks to publish, prepare a release, bump version, or create a patch:
|
|
49
|
+
1. **FIRST** invoke the Test Verification subagent using `runSubagent`
|
|
50
|
+
2. Wait for it to complete and report results
|
|
51
|
+
3. Only proceed if all tests pass
|
|
52
|
+
|
|
53
|
+
The Test Verification agent runs:
|
|
54
|
+
- `npm run compile` (must have 0 errors)
|
|
55
|
+
- `node ./dist/cli.js ./tests/Regression/ -e prelive` (all tests must pass)
|
|
56
|
+
|
|
57
|
+
**Trigger words:** publish, release, version, patch, bump, deploy
|
|
58
|
+
|
|
59
|
+
## Key Files
|
|
60
|
+
|
|
61
|
+
| Purpose | File |
|
|
62
|
+
|---------|------|
|
|
63
|
+
| Syntax highlighting | `syntaxes/norn.tmLanguage.json` |
|
|
64
|
+
| IntelliSense | `src/completionProvider.ts` |
|
|
65
|
+
| Sequence execution | `src/sequenceRunner.ts` |
|
|
66
|
+
| Assertions | `src/assertionRunner.ts` |
|
|
67
|
+
| HTTP requests | `src/httpClient.ts` |
|
|
68
|
+
| CLI | `src/cli.ts` |
|
|
69
|
+
| Parser | `src/parser.ts` |
|
|
70
|
+
| Response panel | `src/responsePanel.ts` |
|
|
71
|
+
| Diagnostics | `src/diagnosticProvider.ts` |
|
|
72
|
+
| Test Explorer | `src/testProvider.ts` |
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the "Norn" extension will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.3.18] - 2026-02-15
|
|
6
|
+
|
|
7
|
+
### Improved
|
|
8
|
+
- **Environment Resolution (Extension + CLI)**: `.nornenv` resolution is now file-aware and uses the closest ancestor file.
|
|
9
|
+
- VS Code execution paths now resolve env variables from the current document path.
|
|
10
|
+
- CLI directory runs now resolve environments per test file path (including nested folders).
|
|
11
|
+
- CodeLens and environment status now reflect the active editor/file context.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **IntelliSense in Request Bodies**:
|
|
15
|
+
- Header completions are suppressed when starting JSON body lines (`{` or `[`).
|
|
16
|
+
- Header suggestions remain available for non-body lines, including raw URL requests (e.g., `POST "https://..."`).
|
|
17
|
+
- **Syntax Highlighting**:
|
|
18
|
+
- Fixed header coloring after endpoint calls where header-group token matching could break on `Content-Type`.
|
|
19
|
+
- Improved assert string highlighting with variable interpolation inside quoted strings.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- **Regression Coverage**:
|
|
23
|
+
- Added env-resolution regression tests for parent vs nested `.nornenv` precedence.
|
|
24
|
+
|
|
25
|
+
## [1.3.17] - 2026-02-14
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- **Import Resolution**: Improved duplicate import handling while preserving circular import detection
|
|
29
|
+
- Re-importing already loaded files is now treated as a no-op instead of a false circular error
|
|
30
|
+
- True cycles are still detected via active import stack tracking
|
|
31
|
+
- **Sequence Var-Request Parsing**: Fixed multi-line `var x = METHOD ...` request collection
|
|
32
|
+
- Headers and JSON/form bodies are now preserved until the next real command boundary
|
|
33
|
+
- Body-only shorthand continues to parse correctly
|
|
34
|
+
- **Syntax Highlighting**: Improved `var` request URL/header/body highlighting in `.norn` files
|
|
35
|
+
- Better distinction between endpoint captures and raw URL captures
|
|
36
|
+
- Header groups and body lines now color consistently in URL-style var requests
|
|
37
|
+
|
|
5
38
|
## [1.3.16] - 2026-02-13
|
|
6
39
|
|
|
7
40
|
### Fixed
|
|
@@ -843,4 +876,4 @@ end sequence
|
|
|
843
876
|
- **IntelliSense**: Autocomplete for methods, headers, variables, and keywords
|
|
844
877
|
- **Diagnostics**: Error highlighting for undefined variables
|
|
845
878
|
- **CLI Tool**: Run requests from terminal with JSON output for CI/CD pipelines
|
|
846
|
-
- **Response Panel**: Webview with formatted JSON and execution results
|
|
879
|
+
- **Response Panel**: Webview with formatted JSON and execution results
|
package/README.md
CHANGED
|
@@ -340,7 +340,7 @@ end sequence
|
|
|
340
340
|
- Circular imports are detected and reported as errors
|
|
341
341
|
- Only named requests `[Name]` and sequences are imported
|
|
342
342
|
- Variables are resolved at import time (baked in), not exported
|
|
343
|
-
- `.nornenv`
|
|
343
|
+
- `.nornenv` resolution is file-aware: Norn walks up from the running file and uses the closest `.nornenv`
|
|
344
344
|
|
|
345
345
|
### API Definition Files (.nornapi)
|
|
346
346
|
|
|
@@ -506,7 +506,8 @@ end sequence
|
|
|
506
506
|
|
|
507
507
|
### Environments
|
|
508
508
|
|
|
509
|
-
Create a `.nornenv` file
|
|
509
|
+
Create a `.nornenv` file to manage environment-specific variables.
|
|
510
|
+
Norn resolves the closest `.nornenv` by walking up from the file being run (or edited):
|
|
510
511
|
|
|
511
512
|
```bash
|
|
512
513
|
# .nornenv file
|
|
@@ -1017,6 +1018,7 @@ end sequence
|
|
|
1017
1018
|
| `var name = value` | Common variable (all environments) |
|
|
1018
1019
|
| `[env:name]` | Start environment section |
|
|
1019
1020
|
| `# comment` | Comment line |
|
|
1021
|
+
| resolution | Closest ancestor `.nornenv` from the current `.norn`/`.nornapi` file |
|
|
1020
1022
|
|
|
1021
1023
|
### API Definitions (.nornapi)
|
|
1022
1024
|
|
package/dist/cli.js
CHANGED
|
@@ -10386,7 +10386,7 @@ var require_ms = __commonJS({
|
|
|
10386
10386
|
options = options || {};
|
|
10387
10387
|
var type = typeof val;
|
|
10388
10388
|
if (type === "string" && val.length > 0) {
|
|
10389
|
-
return
|
|
10389
|
+
return parse2(val);
|
|
10390
10390
|
} else if (type === "number" && isFinite(val)) {
|
|
10391
10391
|
return options.long ? fmtLong(val) : fmtShort(val);
|
|
10392
10392
|
}
|
|
@@ -10394,7 +10394,7 @@ var require_ms = __commonJS({
|
|
|
10394
10394
|
"val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
|
|
10395
10395
|
);
|
|
10396
10396
|
};
|
|
10397
|
-
function
|
|
10397
|
+
function parse2(str) {
|
|
10398
10398
|
str = String(str);
|
|
10399
10399
|
if (str.length > 100) {
|
|
10400
10400
|
return;
|
|
@@ -12106,7 +12106,7 @@ var require_cjs = __commonJS({
|
|
|
12106
12106
|
out.publicSuffix = (_a = hostnameParts[hostnameParts.length - 1]) !== null && _a !== void 0 ? _a : null;
|
|
12107
12107
|
}
|
|
12108
12108
|
var RESULT = getEmptyResult();
|
|
12109
|
-
function
|
|
12109
|
+
function parse2(url2, options = {}) {
|
|
12110
12110
|
return parseImpl(url2, 5, suffixLookup, options, getEmptyResult());
|
|
12111
12111
|
}
|
|
12112
12112
|
function getHostname(url2, options = {}) {
|
|
@@ -12134,7 +12134,7 @@ var require_cjs = __commonJS({
|
|
|
12134
12134
|
exports2.getHostname = getHostname;
|
|
12135
12135
|
exports2.getPublicSuffix = getPublicSuffix2;
|
|
12136
12136
|
exports2.getSubdomain = getSubdomain;
|
|
12137
|
-
exports2.parse =
|
|
12137
|
+
exports2.parse = parse2;
|
|
12138
12138
|
}
|
|
12139
12139
|
});
|
|
12140
12140
|
|
|
@@ -15684,24 +15684,24 @@ var require_fast_uri = __commonJS({
|
|
|
15684
15684
|
function normalize(uri, options) {
|
|
15685
15685
|
if (typeof uri === "string") {
|
|
15686
15686
|
uri = /** @type {T} */
|
|
15687
|
-
serialize(
|
|
15687
|
+
serialize(parse2(uri, options), options);
|
|
15688
15688
|
} else if (typeof uri === "object") {
|
|
15689
15689
|
uri = /** @type {T} */
|
|
15690
|
-
|
|
15690
|
+
parse2(serialize(uri, options), options);
|
|
15691
15691
|
}
|
|
15692
15692
|
return uri;
|
|
15693
15693
|
}
|
|
15694
15694
|
function resolve5(baseURI, relativeURI, options) {
|
|
15695
15695
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
15696
|
-
const resolved = resolveComponent(
|
|
15696
|
+
const resolved = resolveComponent(parse2(baseURI, schemelessOptions), parse2(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
15697
15697
|
schemelessOptions.skipEscape = true;
|
|
15698
15698
|
return serialize(resolved, schemelessOptions);
|
|
15699
15699
|
}
|
|
15700
15700
|
function resolveComponent(base, relative2, options, skipNormalization) {
|
|
15701
15701
|
const target = {};
|
|
15702
15702
|
if (!skipNormalization) {
|
|
15703
|
-
base =
|
|
15704
|
-
relative2 =
|
|
15703
|
+
base = parse2(serialize(base, options), options);
|
|
15704
|
+
relative2 = parse2(serialize(relative2, options), options);
|
|
15705
15705
|
}
|
|
15706
15706
|
options = options || {};
|
|
15707
15707
|
if (!options.tolerant && relative2.scheme) {
|
|
@@ -15753,13 +15753,13 @@ var require_fast_uri = __commonJS({
|
|
|
15753
15753
|
function equal(uriA, uriB, options) {
|
|
15754
15754
|
if (typeof uriA === "string") {
|
|
15755
15755
|
uriA = unescape(uriA);
|
|
15756
|
-
uriA = serialize(normalizeComponentEncoding(
|
|
15756
|
+
uriA = serialize(normalizeComponentEncoding(parse2(uriA, options), true), { ...options, skipEscape: true });
|
|
15757
15757
|
} else if (typeof uriA === "object") {
|
|
15758
15758
|
uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
|
|
15759
15759
|
}
|
|
15760
15760
|
if (typeof uriB === "string") {
|
|
15761
15761
|
uriB = unescape(uriB);
|
|
15762
|
-
uriB = serialize(normalizeComponentEncoding(
|
|
15762
|
+
uriB = serialize(normalizeComponentEncoding(parse2(uriB, options), true), { ...options, skipEscape: true });
|
|
15763
15763
|
} else if (typeof uriB === "object") {
|
|
15764
15764
|
uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
|
|
15765
15765
|
}
|
|
@@ -15828,7 +15828,7 @@ var require_fast_uri = __commonJS({
|
|
|
15828
15828
|
return uriTokens.join("");
|
|
15829
15829
|
}
|
|
15830
15830
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
15831
|
-
function
|
|
15831
|
+
function parse2(uri, opts) {
|
|
15832
15832
|
const options = Object.assign({}, opts);
|
|
15833
15833
|
const parsed = {
|
|
15834
15834
|
scheme: void 0,
|
|
@@ -15922,7 +15922,7 @@ var require_fast_uri = __commonJS({
|
|
|
15922
15922
|
resolveComponent,
|
|
15923
15923
|
equal,
|
|
15924
15924
|
serialize,
|
|
15925
|
-
parse:
|
|
15925
|
+
parse: parse2
|
|
15926
15926
|
};
|
|
15927
15927
|
module2.exports = fastUri;
|
|
15928
15928
|
module2.exports.default = fastUri;
|
|
@@ -20273,7 +20273,7 @@ function extractImports(text) {
|
|
|
20273
20273
|
}
|
|
20274
20274
|
return imports;
|
|
20275
20275
|
}
|
|
20276
|
-
async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__PURE__ */ new Set()) {
|
|
20276
|
+
async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__PURE__ */ new Set(), importStack = /* @__PURE__ */ new Set()) {
|
|
20277
20277
|
const imports = extractImports(text);
|
|
20278
20278
|
const errors = [];
|
|
20279
20279
|
const importedContents = [];
|
|
@@ -20287,7 +20287,7 @@ async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__
|
|
|
20287
20287
|
for (const imp of imports) {
|
|
20288
20288
|
const path5 = await import("path");
|
|
20289
20289
|
const absolutePath = path5.resolve(baseDir, imp.path);
|
|
20290
|
-
if (
|
|
20290
|
+
if (importStack.has(absolutePath)) {
|
|
20291
20291
|
errors.push({
|
|
20292
20292
|
path: imp.path,
|
|
20293
20293
|
error: `Circular import detected`,
|
|
@@ -20295,9 +20295,13 @@ async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__
|
|
|
20295
20295
|
});
|
|
20296
20296
|
continue;
|
|
20297
20297
|
}
|
|
20298
|
+
if (alreadyImported.has(absolutePath)) {
|
|
20299
|
+
continue;
|
|
20300
|
+
}
|
|
20298
20301
|
try {
|
|
20299
20302
|
const content = await readFile2(absolutePath);
|
|
20300
20303
|
alreadyImported.add(absolutePath);
|
|
20304
|
+
importStack.add(absolutePath);
|
|
20301
20305
|
resolvedPaths.push(absolutePath);
|
|
20302
20306
|
if (imp.path.endsWith(".nornapi")) {
|
|
20303
20307
|
const apiDef = parseNornApiFile(content);
|
|
@@ -20330,7 +20334,7 @@ async function resolveImports(text, baseDir, readFile2, alreadyImported = /* @__
|
|
|
20330
20334
|
continue;
|
|
20331
20335
|
}
|
|
20332
20336
|
const importDir = path5.dirname(absolutePath);
|
|
20333
|
-
const nestedResult = await resolveImports(content, importDir, readFile2, alreadyImported);
|
|
20337
|
+
const nestedResult = await resolveImports(content, importDir, readFile2, alreadyImported, importStack);
|
|
20334
20338
|
errors.push(...nestedResult.errors);
|
|
20335
20339
|
resolvedPaths.push(...nestedResult.resolvedPaths);
|
|
20336
20340
|
for (const group of nestedResult.headerGroups) {
|
|
@@ -20404,6 +20408,8 @@ end sequence`);
|
|
|
20404
20408
|
error: error.message || "Failed to read file",
|
|
20405
20409
|
lineNumber: imp.lineNumber
|
|
20406
20410
|
});
|
|
20411
|
+
} finally {
|
|
20412
|
+
importStack.delete(absolutePath);
|
|
20407
20413
|
}
|
|
20408
20414
|
}
|
|
20409
20415
|
return {
|
|
@@ -27570,6 +27576,7 @@ function extractStepsFromSequence(content) {
|
|
|
27570
27576
|
const lines = content.split("\n");
|
|
27571
27577
|
const steps = [];
|
|
27572
27578
|
const methodRegex = /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+/i;
|
|
27579
|
+
const stepBoundaryRegex = /^(###|(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+|(test\s+)?sequence\s+|end\s+sequence|end\s+if|endif\b|var\s+|run\s+|print\s+|assert\s+|if\s+|wait\s+|import\s+|return\s+|\[)/i;
|
|
27573
27580
|
let currentRequest = [];
|
|
27574
27581
|
let currentRequestStartLine = -1;
|
|
27575
27582
|
let inRequest = false;
|
|
@@ -27819,11 +27826,11 @@ function extractStepsFromSequence(content) {
|
|
|
27819
27826
|
currentRequestStartLine = lineIdx;
|
|
27820
27827
|
inRequest = true;
|
|
27821
27828
|
} else if (inVarRequest) {
|
|
27822
|
-
if (trimmed
|
|
27823
|
-
currentVarRequest.push(trimmed);
|
|
27824
|
-
} else {
|
|
27829
|
+
if (trimmed !== "" && stepBoundaryRegex.test(trimmed)) {
|
|
27825
27830
|
savePendingVarRequest();
|
|
27826
27831
|
lineIdx--;
|
|
27832
|
+
} else {
|
|
27833
|
+
currentVarRequest.push(trimmed);
|
|
27827
27834
|
}
|
|
27828
27835
|
} else if (inRequest) {
|
|
27829
27836
|
if (trimmed.match(/^var\s+[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*\$\d+/)) {
|
|
@@ -28323,9 +28330,15 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
|
|
|
28323
28330
|
}
|
|
28324
28331
|
const contentLines = step.content.split("\n");
|
|
28325
28332
|
if (contentLines.length > 1) {
|
|
28326
|
-
const
|
|
28327
|
-
const
|
|
28328
|
-
|
|
28333
|
+
const extraLines = contentLines.slice(1).map((line2) => substituteVariables(line2, runtimeVariables));
|
|
28334
|
+
const hasExplicitBlank = extraLines.some((line2) => line2.trim() === "");
|
|
28335
|
+
const firstNonEmpty = extraLines.find((line2) => line2.trim() !== "");
|
|
28336
|
+
const startsWithHeader = firstNonEmpty ? /^[A-Za-z0-9\-_]+\s*:/.test(firstNonEmpty.trim()) : false;
|
|
28337
|
+
if (hasExplicitBlank || startsWithHeader) {
|
|
28338
|
+
requestText += "\n" + extraLines.join("\n");
|
|
28339
|
+
} else {
|
|
28340
|
+
requestText += "\n\n" + extraLines.join("\n");
|
|
28341
|
+
}
|
|
28329
28342
|
}
|
|
28330
28343
|
const requestParsed = parserHttpRequest(requestText, runtimeVariables);
|
|
28331
28344
|
const retryOpts = parsed.retryCount ? {
|
|
@@ -30256,15 +30269,27 @@ function generateHtmlReportFromResponse(response, testName, options) {
|
|
|
30256
30269
|
|
|
30257
30270
|
// src/cli.ts
|
|
30258
30271
|
var ENV_FILENAME = ".nornenv";
|
|
30272
|
+
function getEnvSearchStartDirectory(targetPath) {
|
|
30273
|
+
const resolvedPath = path4.resolve(targetPath);
|
|
30274
|
+
try {
|
|
30275
|
+
const stats = fs5.statSync(resolvedPath);
|
|
30276
|
+
return stats.isDirectory() ? resolvedPath : path4.dirname(resolvedPath);
|
|
30277
|
+
} catch {
|
|
30278
|
+
return path4.dirname(resolvedPath);
|
|
30279
|
+
}
|
|
30280
|
+
}
|
|
30259
30281
|
function findEnvFileFromPath(filePath) {
|
|
30260
|
-
let dir =
|
|
30261
|
-
|
|
30262
|
-
while (dir !== root) {
|
|
30282
|
+
let dir = getEnvSearchStartDirectory(filePath);
|
|
30283
|
+
while (true) {
|
|
30263
30284
|
const envPath = path4.join(dir, ENV_FILENAME);
|
|
30264
30285
|
if (fs5.existsSync(envPath)) {
|
|
30265
30286
|
return envPath;
|
|
30266
30287
|
}
|
|
30267
|
-
|
|
30288
|
+
const parentDir = path4.dirname(dir);
|
|
30289
|
+
if (parentDir === dir) {
|
|
30290
|
+
break;
|
|
30291
|
+
}
|
|
30292
|
+
dir = parentDir;
|
|
30268
30293
|
}
|
|
30269
30294
|
return void 0;
|
|
30270
30295
|
}
|
|
@@ -30320,6 +30345,57 @@ function parseEnvFile(content) {
|
|
|
30320
30345
|
}
|
|
30321
30346
|
return config;
|
|
30322
30347
|
}
|
|
30348
|
+
function resolveEnvironmentForPath(targetPath, selectedEnv) {
|
|
30349
|
+
const envFilePath = findEnvFileFromPath(targetPath);
|
|
30350
|
+
if (!envFilePath) {
|
|
30351
|
+
return {
|
|
30352
|
+
variables: {},
|
|
30353
|
+
secretNames: /* @__PURE__ */ new Set(),
|
|
30354
|
+
secretValues: /* @__PURE__ */ new Map(),
|
|
30355
|
+
availableEnvironments: []
|
|
30356
|
+
};
|
|
30357
|
+
}
|
|
30358
|
+
const envContent = fs5.readFileSync(envFilePath, "utf-8");
|
|
30359
|
+
const envConfig = parseEnvFile(envContent);
|
|
30360
|
+
const variables = { ...envConfig.common };
|
|
30361
|
+
const secretNames = new Set(envConfig.secretNames);
|
|
30362
|
+
const secretValues = new Map(envConfig.secretValues);
|
|
30363
|
+
const availableEnvironments = envConfig.environments.map((e) => e.name);
|
|
30364
|
+
if (selectedEnv) {
|
|
30365
|
+
const targetEnv = envConfig.environments.find((e) => e.name === selectedEnv);
|
|
30366
|
+
if (!targetEnv) {
|
|
30367
|
+
return {
|
|
30368
|
+
envFilePath,
|
|
30369
|
+
variables,
|
|
30370
|
+
secretNames,
|
|
30371
|
+
secretValues,
|
|
30372
|
+
availableEnvironments,
|
|
30373
|
+
envNotFound: selectedEnv
|
|
30374
|
+
};
|
|
30375
|
+
}
|
|
30376
|
+
Object.assign(variables, targetEnv.variables);
|
|
30377
|
+
for (const [name, value] of Object.entries(targetEnv.variables)) {
|
|
30378
|
+
if (secretNames.has(name)) {
|
|
30379
|
+
secretValues.set(name, value);
|
|
30380
|
+
}
|
|
30381
|
+
}
|
|
30382
|
+
}
|
|
30383
|
+
return {
|
|
30384
|
+
envFilePath,
|
|
30385
|
+
variables,
|
|
30386
|
+
secretNames,
|
|
30387
|
+
secretValues,
|
|
30388
|
+
availableEnvironments
|
|
30389
|
+
};
|
|
30390
|
+
}
|
|
30391
|
+
function mergeSecrets(targetNames, targetValues, sourceNames, sourceValues) {
|
|
30392
|
+
for (const name of sourceNames) {
|
|
30393
|
+
targetNames.add(name);
|
|
30394
|
+
}
|
|
30395
|
+
for (const [name, value] of sourceValues) {
|
|
30396
|
+
targetValues.set(name, value);
|
|
30397
|
+
}
|
|
30398
|
+
}
|
|
30323
30399
|
function generateTimestamp() {
|
|
30324
30400
|
const now = /* @__PURE__ */ new Date();
|
|
30325
30401
|
const year = now.getFullYear();
|
|
@@ -30726,38 +30802,8 @@ async function main() {
|
|
|
30726
30802
|
const allErrors = [];
|
|
30727
30803
|
let overallSuccess = true;
|
|
30728
30804
|
const startTime = Date.now();
|
|
30729
|
-
const
|
|
30730
|
-
|
|
30731
|
-
let redaction = createRedactionOptions(/* @__PURE__ */ new Set(), /* @__PURE__ */ new Map(), !options.noRedact);
|
|
30732
|
-
if (envFilePath) {
|
|
30733
|
-
const envContent = fs5.readFileSync(envFilePath, "utf-8");
|
|
30734
|
-
const envConfig = parseEnvFile(envContent);
|
|
30735
|
-
envVariables = { ...envConfig.common };
|
|
30736
|
-
redaction = createRedactionOptions(
|
|
30737
|
-
envConfig.secretNames,
|
|
30738
|
-
envConfig.secretValues,
|
|
30739
|
-
!options.noRedact
|
|
30740
|
-
);
|
|
30741
|
-
if (options.env) {
|
|
30742
|
-
const targetEnv = envConfig.environments.find((e) => e.name === options.env);
|
|
30743
|
-
if (!targetEnv) {
|
|
30744
|
-
console.error(`Error: Environment '${options.env}' not found in .nornenv`);
|
|
30745
|
-
console.error(`Available environments: ${envConfig.environments.map((e) => e.name).join(", ") || "none"}`);
|
|
30746
|
-
process.exit(1);
|
|
30747
|
-
}
|
|
30748
|
-
Object.assign(envVariables, targetEnv.variables);
|
|
30749
|
-
for (const [name, value] of Object.entries(targetEnv.variables)) {
|
|
30750
|
-
if (envConfig.secretNames.has(name)) {
|
|
30751
|
-
redaction.secretValues.set(name, value);
|
|
30752
|
-
}
|
|
30753
|
-
}
|
|
30754
|
-
if (options.verbose) {
|
|
30755
|
-
console.log(colors.info(`Using environment: ${options.env}`));
|
|
30756
|
-
}
|
|
30757
|
-
}
|
|
30758
|
-
} else if (options.env) {
|
|
30759
|
-
console.error(colors.warning(`Warning: --env specified but no .nornenv file found`));
|
|
30760
|
-
}
|
|
30805
|
+
const combinedSecretNames = /* @__PURE__ */ new Set();
|
|
30806
|
+
const combinedSecretValues = /* @__PURE__ */ new Map();
|
|
30761
30807
|
let tagFilterOptions = void 0;
|
|
30762
30808
|
if (options.tagFilters.length > 0) {
|
|
30763
30809
|
tagFilterOptions = {
|
|
@@ -30776,9 +30822,22 @@ async function main() {
|
|
|
30776
30822
|
}
|
|
30777
30823
|
if (options.sequence || options.request) {
|
|
30778
30824
|
const filePath = filesToRun[0];
|
|
30825
|
+
const resolvedEnv = resolveEnvironmentForPath(filePath, options.env);
|
|
30826
|
+
if (resolvedEnv.envNotFound) {
|
|
30827
|
+
console.error(`Error: Environment '${resolvedEnv.envNotFound}' not found in .nornenv`);
|
|
30828
|
+
console.error(`Available environments: ${resolvedEnv.availableEnvironments.join(", ") || "none"}`);
|
|
30829
|
+
process.exit(1);
|
|
30830
|
+
}
|
|
30831
|
+
if (!resolvedEnv.envFilePath && options.env) {
|
|
30832
|
+
console.error(colors.warning(`Warning: --env specified but no .nornenv file found`));
|
|
30833
|
+
} else if (resolvedEnv.envFilePath && options.env && options.verbose) {
|
|
30834
|
+
console.log(colors.info(`Using environment: ${options.env}`));
|
|
30835
|
+
}
|
|
30836
|
+
mergeSecrets(combinedSecretNames, combinedSecretValues, resolvedEnv.secretNames, resolvedEnv.secretValues);
|
|
30837
|
+
const redaction2 = createRedactionOptions(combinedSecretNames, combinedSecretValues, !options.noRedact);
|
|
30779
30838
|
const fileContent = fs5.readFileSync(filePath, "utf-8");
|
|
30780
30839
|
const fileVariables = extractFileLevelVariables(fileContent);
|
|
30781
|
-
const variables = { ...
|
|
30840
|
+
const variables = { ...resolvedEnv.variables, ...fileVariables };
|
|
30782
30841
|
const cookieJar = createCookieJar();
|
|
30783
30842
|
const workingDir = path4.dirname(filePath);
|
|
30784
30843
|
const importResult = await resolveImports(
|
|
@@ -30822,7 +30881,7 @@ ${fileContent}` : fileContent;
|
|
|
30822
30881
|
console.log(JSON.stringify({ success: response.status >= 200 && response.status < 400, results: [response] }, null, 2));
|
|
30823
30882
|
} else {
|
|
30824
30883
|
const isSuccess = response.status >= 200 && response.status < 300;
|
|
30825
|
-
const lines = formatResponse(response, { colors, verbose: options.verbose, showDetails: !isSuccess || options.verbose, redaction });
|
|
30884
|
+
const lines = formatResponse(response, { colors, verbose: options.verbose, showDetails: !isSuccess || options.verbose, redaction: redaction2 });
|
|
30826
30885
|
for (const line2 of lines) {
|
|
30827
30886
|
console.log(line2);
|
|
30828
30887
|
}
|
|
@@ -30865,7 +30924,7 @@ ${fileContent}` : fileContent;
|
|
|
30865
30924
|
if (options.output === "json") {
|
|
30866
30925
|
console.log(JSON.stringify({ success: seqResult.success, results: [seqResult] }, null, 2));
|
|
30867
30926
|
} else {
|
|
30868
|
-
const lines = formatSequenceResult(seqResult, { colors, verbose: options.verbose, redaction });
|
|
30927
|
+
const lines = formatSequenceResult(seqResult, { colors, verbose: options.verbose, redaction: redaction2 });
|
|
30869
30928
|
for (const line2 of lines) {
|
|
30870
30929
|
console.log(line2);
|
|
30871
30930
|
}
|
|
@@ -30899,9 +30958,21 @@ ${fileContent}` : fileContent;
|
|
|
30899
30958
|
console.log("");
|
|
30900
30959
|
}
|
|
30901
30960
|
for (const filePath of filesToRun) {
|
|
30961
|
+
const resolvedEnv = resolveEnvironmentForPath(filePath, options.env);
|
|
30962
|
+
if (resolvedEnv.envNotFound) {
|
|
30963
|
+
console.error(`Error: Environment '${resolvedEnv.envNotFound}' not found in .nornenv`);
|
|
30964
|
+
console.error(`Available environments: ${resolvedEnv.availableEnvironments.join(", ") || "none"}`);
|
|
30965
|
+
process.exit(1);
|
|
30966
|
+
}
|
|
30967
|
+
if (!resolvedEnv.envFilePath && options.env) {
|
|
30968
|
+
const relPath = isDirectory ? path4.relative(inputPath, filePath) : path4.basename(filePath);
|
|
30969
|
+
console.error(colors.warning(`Warning: --env specified but no .nornenv file found for ${relPath}`));
|
|
30970
|
+
}
|
|
30971
|
+
mergeSecrets(combinedSecretNames, combinedSecretValues, resolvedEnv.secretNames, resolvedEnv.secretValues);
|
|
30972
|
+
const redaction2 = createRedactionOptions(combinedSecretNames, combinedSecretValues, !options.noRedact);
|
|
30902
30973
|
const fileContent = fs5.readFileSync(filePath, "utf-8");
|
|
30903
30974
|
const fileVariables = extractFileLevelVariables(fileContent);
|
|
30904
|
-
const variables = { ...
|
|
30975
|
+
const variables = { ...resolvedEnv.variables, ...fileVariables };
|
|
30905
30976
|
const cookieJar = createCookieJar();
|
|
30906
30977
|
const workingDir = path4.dirname(filePath);
|
|
30907
30978
|
const importResult = await resolveImports(
|
|
@@ -30951,7 +31022,7 @@ ${fileContent}` : fileContent;
|
|
|
30951
31022
|
}
|
|
30952
31023
|
if (options.output !== "json") {
|
|
30953
31024
|
for (const seqResult of seqResults) {
|
|
30954
|
-
const lines = formatSequenceResult(seqResult, { colors, verbose: options.verbose, redaction });
|
|
31025
|
+
const lines = formatSequenceResult(seqResult, { colors, verbose: options.verbose, redaction: redaction2 });
|
|
30955
31026
|
for (const line2 of lines) {
|
|
30956
31027
|
console.log(line2);
|
|
30957
31028
|
}
|
|
@@ -30959,6 +31030,7 @@ ${fileContent}` : fileContent;
|
|
|
30959
31030
|
}
|
|
30960
31031
|
}
|
|
30961
31032
|
const totalDuration = Date.now() - startTime;
|
|
31033
|
+
const redaction = createRedactionOptions(combinedSecretNames, combinedSecretValues, !options.noRedact);
|
|
30962
31034
|
const result = {
|
|
30963
31035
|
success: overallSuccess,
|
|
30964
31036
|
type: "all-sequences",
|