norn-cli 1.4.0 → 1.4.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/.norn-cache/swagger-body-intellisense.json +1 -1
- package/CHANGELOG.md +16 -0
- package/out/chatParticipant.js +722 -0
- package/out/cli.js +99 -36
- package/out/codeLensProvider.js +14 -20
- package/out/completionProvider.js +543 -25
- package/out/coverageCalculator.js +250 -169
- package/out/coveragePanel.js +7 -4
- package/out/diagnosticProvider.js +135 -2
- package/out/environmentProvider.js +96 -27
- package/out/extension.js +98 -9
- package/out/nornPrompt.js +580 -0
- package/out/swaggerBodyIntellisenseCache.js +147 -0
- package/out/swaggerParser.js +154 -74
- package/out/test/coverageCalculator.test.js +100 -0
- package/out/testProvider.js +1 -1
- package/package.json +1 -1
package/out/cli.js
CHANGED
|
@@ -58,18 +58,31 @@ const response_1 = require("./cli/formatters/response");
|
|
|
58
58
|
const junit_1 = require("./cli/reporters/junit");
|
|
59
59
|
const html_1 = require("./cli/reporters/html");
|
|
60
60
|
const ENV_FILENAME = '.nornenv';
|
|
61
|
+
function getEnvSearchStartDirectory(targetPath) {
|
|
62
|
+
const resolvedPath = path.resolve(targetPath);
|
|
63
|
+
try {
|
|
64
|
+
const stats = fs.statSync(resolvedPath);
|
|
65
|
+
return stats.isDirectory() ? resolvedPath : path.dirname(resolvedPath);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return path.dirname(resolvedPath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
61
71
|
/**
|
|
62
72
|
* Finds .nornenv file relative to a specific file path (for CLI usage)
|
|
63
73
|
*/
|
|
64
74
|
function findEnvFileFromPath(filePath) {
|
|
65
|
-
let dir =
|
|
66
|
-
|
|
67
|
-
while (dir !== root) {
|
|
75
|
+
let dir = getEnvSearchStartDirectory(filePath);
|
|
76
|
+
while (true) {
|
|
68
77
|
const envPath = path.join(dir, ENV_FILENAME);
|
|
69
78
|
if (fs.existsSync(envPath)) {
|
|
70
79
|
return envPath;
|
|
71
80
|
}
|
|
72
|
-
|
|
81
|
+
const parentDir = path.dirname(dir);
|
|
82
|
+
if (parentDir === dir) {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
dir = parentDir;
|
|
73
86
|
}
|
|
74
87
|
return undefined;
|
|
75
88
|
}
|
|
@@ -134,6 +147,57 @@ function parseEnvFile(content) {
|
|
|
134
147
|
}
|
|
135
148
|
return config;
|
|
136
149
|
}
|
|
150
|
+
function resolveEnvironmentForPath(targetPath, selectedEnv) {
|
|
151
|
+
const envFilePath = findEnvFileFromPath(targetPath);
|
|
152
|
+
if (!envFilePath) {
|
|
153
|
+
return {
|
|
154
|
+
variables: {},
|
|
155
|
+
secretNames: new Set(),
|
|
156
|
+
secretValues: new Map(),
|
|
157
|
+
availableEnvironments: []
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const envContent = fs.readFileSync(envFilePath, 'utf-8');
|
|
161
|
+
const envConfig = parseEnvFile(envContent);
|
|
162
|
+
const variables = { ...envConfig.common };
|
|
163
|
+
const secretNames = new Set(envConfig.secretNames);
|
|
164
|
+
const secretValues = new Map(envConfig.secretValues);
|
|
165
|
+
const availableEnvironments = envConfig.environments.map(e => e.name);
|
|
166
|
+
if (selectedEnv) {
|
|
167
|
+
const targetEnv = envConfig.environments.find(e => e.name === selectedEnv);
|
|
168
|
+
if (!targetEnv) {
|
|
169
|
+
return {
|
|
170
|
+
envFilePath,
|
|
171
|
+
variables,
|
|
172
|
+
secretNames,
|
|
173
|
+
secretValues,
|
|
174
|
+
availableEnvironments,
|
|
175
|
+
envNotFound: selectedEnv
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
Object.assign(variables, targetEnv.variables);
|
|
179
|
+
for (const [name, value] of Object.entries(targetEnv.variables)) {
|
|
180
|
+
if (secretNames.has(name)) {
|
|
181
|
+
secretValues.set(name, value);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
envFilePath,
|
|
187
|
+
variables,
|
|
188
|
+
secretNames,
|
|
189
|
+
secretValues,
|
|
190
|
+
availableEnvironments
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function mergeSecrets(targetNames, targetValues, sourceNames, sourceValues) {
|
|
194
|
+
for (const name of sourceNames) {
|
|
195
|
+
targetNames.add(name);
|
|
196
|
+
}
|
|
197
|
+
for (const [name, value] of sourceValues) {
|
|
198
|
+
targetValues.set(name, value);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
137
201
|
/**
|
|
138
202
|
* Generate a timestamp string for report filenames
|
|
139
203
|
* Format: YYYY-MM-DD-HHmmss
|
|
@@ -576,36 +640,8 @@ async function main() {
|
|
|
576
640
|
const allErrors = [];
|
|
577
641
|
let overallSuccess = true;
|
|
578
642
|
const startTime = Date.now();
|
|
579
|
-
|
|
580
|
-
const
|
|
581
|
-
let envVariables = {};
|
|
582
|
-
let redaction = (0, redaction_1.createRedactionOptions)(new Set(), new Map(), !options.noRedact);
|
|
583
|
-
if (envFilePath) {
|
|
584
|
-
const envContent = fs.readFileSync(envFilePath, 'utf-8');
|
|
585
|
-
const envConfig = parseEnvFile(envContent);
|
|
586
|
-
envVariables = { ...envConfig.common };
|
|
587
|
-
redaction = (0, redaction_1.createRedactionOptions)(envConfig.secretNames, envConfig.secretValues, !options.noRedact);
|
|
588
|
-
if (options.env) {
|
|
589
|
-
const targetEnv = envConfig.environments.find(e => e.name === options.env);
|
|
590
|
-
if (!targetEnv) {
|
|
591
|
-
console.error(`Error: Environment '${options.env}' not found in .nornenv`);
|
|
592
|
-
console.error(`Available environments: ${envConfig.environments.map(e => e.name).join(', ') || 'none'}`);
|
|
593
|
-
process.exit(1);
|
|
594
|
-
}
|
|
595
|
-
Object.assign(envVariables, targetEnv.variables);
|
|
596
|
-
for (const [name, value] of Object.entries(targetEnv.variables)) {
|
|
597
|
-
if (envConfig.secretNames.has(name)) {
|
|
598
|
-
redaction.secretValues.set(name, value);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
if (options.verbose) {
|
|
602
|
-
console.log(colors.info(`Using environment: ${options.env}`));
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
else if (options.env) {
|
|
607
|
-
console.error(colors.warning(`Warning: --env specified but no .nornenv file found`));
|
|
608
|
-
}
|
|
643
|
+
const combinedSecretNames = new Set();
|
|
644
|
+
const combinedSecretValues = new Map();
|
|
609
645
|
let tagFilterOptions = undefined;
|
|
610
646
|
if (options.tagFilters.length > 0) {
|
|
611
647
|
tagFilterOptions = {
|
|
@@ -627,9 +663,23 @@ async function main() {
|
|
|
627
663
|
// Single file mode with specific sequence/request
|
|
628
664
|
if (options.sequence || options.request) {
|
|
629
665
|
const filePath = filesToRun[0];
|
|
666
|
+
const resolvedEnv = resolveEnvironmentForPath(filePath, options.env);
|
|
667
|
+
if (resolvedEnv.envNotFound) {
|
|
668
|
+
console.error(`Error: Environment '${resolvedEnv.envNotFound}' not found in .nornenv`);
|
|
669
|
+
console.error(`Available environments: ${resolvedEnv.availableEnvironments.join(', ') || 'none'}`);
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
if (!resolvedEnv.envFilePath && options.env) {
|
|
673
|
+
console.error(colors.warning(`Warning: --env specified but no .nornenv file found`));
|
|
674
|
+
}
|
|
675
|
+
else if (resolvedEnv.envFilePath && options.env && options.verbose) {
|
|
676
|
+
console.log(colors.info(`Using environment: ${options.env}`));
|
|
677
|
+
}
|
|
678
|
+
mergeSecrets(combinedSecretNames, combinedSecretValues, resolvedEnv.secretNames, resolvedEnv.secretValues);
|
|
679
|
+
const redaction = (0, redaction_1.createRedactionOptions)(combinedSecretNames, combinedSecretValues, !options.noRedact);
|
|
630
680
|
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
631
681
|
const fileVariables = (0, parser_1.extractFileLevelVariables)(fileContent);
|
|
632
|
-
const variables = { ...
|
|
682
|
+
const variables = { ...resolvedEnv.variables, ...fileVariables };
|
|
633
683
|
const cookieJar = (0, httpClient_1.createCookieJar)();
|
|
634
684
|
const workingDir = path.dirname(filePath);
|
|
635
685
|
const importResult = await (0, parser_1.resolveImports)(fileContent, workingDir, async (importPath) => fsPromises.readFile(importPath, 'utf-8'));
|
|
@@ -748,9 +798,21 @@ async function main() {
|
|
|
748
798
|
}
|
|
749
799
|
// Run tests from each file
|
|
750
800
|
for (const filePath of filesToRun) {
|
|
801
|
+
const resolvedEnv = resolveEnvironmentForPath(filePath, options.env);
|
|
802
|
+
if (resolvedEnv.envNotFound) {
|
|
803
|
+
console.error(`Error: Environment '${resolvedEnv.envNotFound}' not found in .nornenv`);
|
|
804
|
+
console.error(`Available environments: ${resolvedEnv.availableEnvironments.join(', ') || 'none'}`);
|
|
805
|
+
process.exit(1);
|
|
806
|
+
}
|
|
807
|
+
if (!resolvedEnv.envFilePath && options.env) {
|
|
808
|
+
const relPath = isDirectory ? path.relative(inputPath, filePath) : path.basename(filePath);
|
|
809
|
+
console.error(colors.warning(`Warning: --env specified but no .nornenv file found for ${relPath}`));
|
|
810
|
+
}
|
|
811
|
+
mergeSecrets(combinedSecretNames, combinedSecretValues, resolvedEnv.secretNames, resolvedEnv.secretValues);
|
|
812
|
+
const redaction = (0, redaction_1.createRedactionOptions)(combinedSecretNames, combinedSecretValues, !options.noRedact);
|
|
751
813
|
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
752
814
|
const fileVariables = (0, parser_1.extractFileLevelVariables)(fileContent);
|
|
753
|
-
const variables = { ...
|
|
815
|
+
const variables = { ...resolvedEnv.variables, ...fileVariables };
|
|
754
816
|
const cookieJar = (0, httpClient_1.createCookieJar)();
|
|
755
817
|
const workingDir = path.dirname(filePath);
|
|
756
818
|
const importResult = await (0, parser_1.resolveImports)(fileContent, workingDir, async (importPath) => fsPromises.readFile(importPath, 'utf-8'));
|
|
@@ -817,6 +879,7 @@ async function main() {
|
|
|
817
879
|
}
|
|
818
880
|
}
|
|
819
881
|
const totalDuration = Date.now() - startTime;
|
|
882
|
+
const redaction = (0, redaction_1.createRedactionOptions)(combinedSecretNames, combinedSecretValues, !options.noRedact);
|
|
820
883
|
// Build result object for reporting
|
|
821
884
|
const result = {
|
|
822
885
|
success: overallSuccess,
|
package/out/codeLensProvider.js
CHANGED
|
@@ -38,16 +38,13 @@ exports.updateCodeLensCoverage = updateCodeLensCoverage;
|
|
|
38
38
|
const vscode = __importStar(require("vscode"));
|
|
39
39
|
const environmentProvider_1 = require("./environmentProvider");
|
|
40
40
|
// Cached coverage percentage for CodeLens display
|
|
41
|
-
let cachedCoveragePercentage = null;
|
|
42
|
-
let cachedCoverageTotal = 0;
|
|
43
|
-
let cachedCoverageCovered = 0;
|
|
44
41
|
/**
|
|
45
42
|
* Update the cached coverage for CodeLens display
|
|
46
43
|
*/
|
|
47
44
|
function updateCodeLensCoverage(percentage, total, covered) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
void percentage;
|
|
46
|
+
void total;
|
|
47
|
+
void covered;
|
|
51
48
|
}
|
|
52
49
|
/**
|
|
53
50
|
* Checks if a sequence declaration has only optional parameters (all have defaults).
|
|
@@ -115,13 +112,13 @@ class HttpCodeLensProvider {
|
|
|
115
112
|
* Gets the environment display text for CodeLens
|
|
116
113
|
* Returns 'add' if no env file exists, environment name if it does
|
|
117
114
|
*/
|
|
118
|
-
getEnvDisplay() {
|
|
119
|
-
const envFile = (0, environmentProvider_1.
|
|
115
|
+
getEnvDisplay(documentPath) {
|
|
116
|
+
const envFile = (0, environmentProvider_1.findEnvFileFromPath)(documentPath);
|
|
120
117
|
if (!envFile) {
|
|
121
118
|
return { text: '+ Add env file', hasEnvFile: false };
|
|
122
119
|
}
|
|
123
120
|
const activeEnv = (0, environmentProvider_1.getActiveEnvironment)();
|
|
124
|
-
const availableEnvs = (0, environmentProvider_1.getAvailableEnvironments)();
|
|
121
|
+
const availableEnvs = (0, environmentProvider_1.getAvailableEnvironments)(envFile);
|
|
125
122
|
if (availableEnvs.length === 0) {
|
|
126
123
|
return { text: 'env: (empty)', hasEnvFile: true };
|
|
127
124
|
}
|
|
@@ -137,7 +134,7 @@ class HttpCodeLensProvider {
|
|
|
137
134
|
// Match matchesSchema assertions: assert $1.body matchesSchema "./schema.json"
|
|
138
135
|
// Note: .+? is non-greedy to not consume matchesSchema
|
|
139
136
|
const matchesSchemaRegex = /^\s*assert\s+.+?\s+matchesSchema\s+["']?([^"']+)["']?/i;
|
|
140
|
-
const envInfo = this.getEnvDisplay();
|
|
137
|
+
const envInfo = this.getEnvDisplay(document.uri.fsPath);
|
|
141
138
|
const isNornApiFile = document.languageId === 'nornapi';
|
|
142
139
|
const isNornFile = document.languageId === 'norn';
|
|
143
140
|
// Track if we're inside a sequence
|
|
@@ -163,16 +160,13 @@ class HttpCodeLensProvider {
|
|
|
163
160
|
arguments: [swaggerMatch[1], document.uri.fsPath],
|
|
164
161
|
tooltip: 'Generate JSON Schema files from OpenAPI response definitions'
|
|
165
162
|
}));
|
|
166
|
-
// Add coverage CodeLens
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
tooltip: `API Coverage: ${cachedCoverageCovered} of ${cachedCoverageTotal} response codes tested. Click to view details.`
|
|
174
|
-
}));
|
|
175
|
-
}
|
|
163
|
+
// Add file-scoped coverage CodeLens
|
|
164
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
165
|
+
title: '$(graph) Show Coverage',
|
|
166
|
+
command: 'norn.showCoverage',
|
|
167
|
+
arguments: [document.uri.fsPath],
|
|
168
|
+
tooltip: 'Show API coverage for this .nornapi file (includes this folder and subfolders only).'
|
|
169
|
+
}));
|
|
176
170
|
continue;
|
|
177
171
|
}
|
|
178
172
|
}
|