norn-cli 2.3.0 → 2.4.0
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/.claude/skills/norn-social-campaign/SKILL.md +70 -0
- package/CHANGELOG.md +6 -0
- package/demos/nornenv-region-refactor/README.md +64 -0
- package/dist/cli.js +360 -1
- package/out/apiResponseIntellisenseCache.js +394 -0
- package/out/assertionRunner.js +567 -0
- package/out/cacheDir.js +136 -0
- package/out/chatParticipant.js +763 -0
- package/out/cli/colors.js +127 -0
- package/out/cli/formatters/assertion.js +102 -0
- package/out/cli/formatters/index.js +23 -0
- package/out/cli/formatters/response.js +106 -0
- package/out/cli/formatters/summary.js +246 -0
- package/out/cli/redaction.js +237 -0
- package/out/cli/reporters/html.js +689 -0
- package/out/cli/reporters/index.js +22 -0
- package/out/cli/reporters/junit.js +226 -0
- package/out/codeLensProvider.js +351 -0
- package/out/compareContentProvider.js +85 -0
- package/out/completionProvider.js +3739 -0
- package/out/contractAssertionSummary.js +225 -0
- package/out/contractDecorationProvider.js +243 -0
- package/out/coverageCalculator.js +879 -0
- package/out/coveragePanel.js +597 -0
- package/out/debug/breakpointResolver.js +84 -0
- package/out/debug/breakpoints.js +52 -0
- package/out/debug/nornDebugAdapter.js +166 -0
- package/out/debug/nornDebugSession.js +613 -0
- package/out/debug/sequenceLocationIndex.js +77 -0
- package/out/debug/types.js +3 -0
- package/out/deepClone.js +21 -0
- package/out/diagnosticProvider.js +2554 -0
- package/out/environmentParser.js +736 -0
- package/out/environmentProvider.js +544 -0
- package/out/environmentTemplates.js +146 -0
- package/out/errors/formatError.js +113 -0
- package/out/errors/nornError.js +29 -0
- package/out/formUrlEncoded.js +89 -0
- package/out/httpClient.js +348 -0
- package/out/httpRuntimeOptions.js +16 -0
- package/out/importErrors.js +31 -0
- package/out/inlayHintResolver.js +70 -0
- package/out/jsonFileReader.js +323 -0
- package/out/mcpClient.js +193 -0
- package/out/mcpConfig.js +184 -0
- package/out/mcpToolIntellisenseCache.js +96 -0
- package/out/mcpToolSchema.js +50 -0
- package/out/nornConfig.js +132 -0
- package/out/nornHoverProvider.js +124 -0
- package/out/nornInlayHintsProvider.js +191 -0
- package/out/nornPrompt.js +755 -0
- package/out/nornSqlParser.js +286 -0
- package/out/nornapiHoverProvider.js +135 -0
- package/out/nornapiInlayHintsProvider.js +94 -0
- package/out/nornapiParser.js +324 -0
- package/out/nornenvCodeActionProvider.js +101 -0
- package/out/nornenvDecorationProvider.js +239 -0
- package/out/nornenvFoldingProvider.js +63 -0
- package/out/nornenvHoverProvider.js +114 -0
- package/out/nornenvInlayHintsProvider.js +99 -0
- package/out/nornenvLanguageModel.js +187 -0
- package/out/nornenvRegionRefactor.js +267 -0
- package/out/nornsqlHoverProvider.js +95 -0
- package/out/nornsqlInlayHintsProvider.js +114 -0
- package/out/parser.js +839 -0
- package/out/pathAccess.js +28 -0
- package/out/postmanImportPanel.js +732 -0
- package/out/postmanImportPlanner.js +1155 -0
- package/out/postmanImportSidebarView.js +532 -0
- package/out/quotedString.js +35 -0
- package/out/requestPreparation.js +179 -0
- package/out/requestValidation.js +146 -0
- package/out/responsePanel.js +7754 -0
- package/out/schemaGenerator.js +562 -0
- package/out/scriptRunner.js +419 -0
- package/out/secrets/cliSecrets.js +415 -0
- package/out/secrets/crypto.js +105 -0
- package/out/secrets/envFileSecrets.js +177 -0
- package/out/secrets/keyStore.js +259 -0
- package/out/sequenceDeclaration.js +15 -0
- package/out/sequenceRunner.js +3590 -0
- package/out/sqlAdapterRunner.js +122 -0
- package/out/sqlBuiltInAdapters.js +604 -0
- package/out/sqlConfig.js +184 -0
- package/out/starterCatalog.js +554 -0
- package/out/stringUtils.js +25 -0
- package/out/swaggerBodyIntellisenseCache.js +114 -0
- package/out/swaggerParser.js +464 -0
- package/out/testProvider.js +767 -0
- package/out/theoryCaseLoader.js +113 -0
- package/out/validationCache.js +211 -0
- package/package.json +6 -1
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.onDidChangeActiveEnvironment = exports.parseEnvFile = void 0;
|
|
37
|
+
exports.initializeEnvironmentMemento = initializeEnvironmentMemento;
|
|
38
|
+
exports.findEnvFile = findEnvFile;
|
|
39
|
+
exports.findEnvFileFromPath = findEnvFileFromPath;
|
|
40
|
+
exports.loadEnvironmentConfig = loadEnvironmentConfig;
|
|
41
|
+
exports.getEnvironmentSecretErrorDetails = getEnvironmentSecretErrorDetails;
|
|
42
|
+
exports.getEnvironmentSecretErrors = getEnvironmentSecretErrors;
|
|
43
|
+
exports.getEnvironmentSecretNames = getEnvironmentSecretNames;
|
|
44
|
+
exports.promptForMissingEnvironmentSecretKeys = promptForMissingEnvironmentSecretKeys;
|
|
45
|
+
exports.setEnvironmentSessionSecretKey = setEnvironmentSessionSecretKey;
|
|
46
|
+
exports.clearEnvironmentSessionSecretKeys = clearEnvironmentSessionSecretKeys;
|
|
47
|
+
exports.getActiveEnvironment = getActiveEnvironment;
|
|
48
|
+
exports.setActiveEnvironment = setActiveEnvironment;
|
|
49
|
+
exports.getEnvironmentVariables = getEnvironmentVariables;
|
|
50
|
+
exports.getEnvironmentImportErrors = getEnvironmentImportErrors;
|
|
51
|
+
exports.getEnvironmentImportErrorDetails = getEnvironmentImportErrorDetails;
|
|
52
|
+
exports.getAvailableEnvironments = getAvailableEnvironments;
|
|
53
|
+
exports.createStatusBarItem = createStatusBarItem;
|
|
54
|
+
exports.refreshEnvironmentStatusBar = refreshEnvironmentStatusBar;
|
|
55
|
+
exports.showEnvironmentPicker = showEnvironmentPicker;
|
|
56
|
+
exports.disposeStatusBar = disposeStatusBar;
|
|
57
|
+
exports.createCoverageStatusBarItem = createCoverageStatusBarItem;
|
|
58
|
+
exports.refreshCoverageStatusBarContext = refreshCoverageStatusBarContext;
|
|
59
|
+
exports.updateCoverageStatusBar = updateCoverageStatusBar;
|
|
60
|
+
exports.getCoverageStatusBarItem = getCoverageStatusBarItem;
|
|
61
|
+
const vscode = __importStar(require("vscode"));
|
|
62
|
+
const fs = __importStar(require("fs"));
|
|
63
|
+
const path = __importStar(require("path"));
|
|
64
|
+
const environmentParser_1 = require("./environmentParser");
|
|
65
|
+
const keyStore_1 = require("./secrets/keyStore");
|
|
66
|
+
const environmentTemplates_1 = require("./environmentTemplates");
|
|
67
|
+
var environmentParser_2 = require("./environmentParser");
|
|
68
|
+
Object.defineProperty(exports, "parseEnvFile", { enumerable: true, get: function () { return environmentParser_2.parseEnvFile; } });
|
|
69
|
+
/**
|
|
70
|
+
* Active env is tracked PER `.nornenv` file (keyed by absolute path). In a monorepo with
|
|
71
|
+
* multiple workspace folders each holding their own `.nornenv`, each remembers its own env.
|
|
72
|
+
* The whole map is persisted to a workspace-scoped memento so selections survive restart.
|
|
73
|
+
*/
|
|
74
|
+
const activeEnvironmentByEnvFile = new Map();
|
|
75
|
+
let environmentMemento;
|
|
76
|
+
const ACTIVE_ENV_MEMENTO_KEY = 'norn.activeEnvironmentByEnvFile';
|
|
77
|
+
let statusBarItem;
|
|
78
|
+
let coverageStatusBarItem;
|
|
79
|
+
let hasCoverageData = false;
|
|
80
|
+
const activeEnvironmentEmitter = new vscode.EventEmitter();
|
|
81
|
+
exports.onDidChangeActiveEnvironment = activeEnvironmentEmitter.event;
|
|
82
|
+
/**
|
|
83
|
+
* Loads persisted active-env selections from the workspace memento. Drops entries whose
|
|
84
|
+
* `.nornenv` file no longer exists so stale paths don't accumulate over time.
|
|
85
|
+
* Must be called once during `activate()` before any UI is shown.
|
|
86
|
+
*/
|
|
87
|
+
function initializeEnvironmentMemento(memento) {
|
|
88
|
+
environmentMemento = memento;
|
|
89
|
+
activeEnvironmentByEnvFile.clear();
|
|
90
|
+
const stored = memento.get(ACTIVE_ENV_MEMENTO_KEY, []);
|
|
91
|
+
let changed = false;
|
|
92
|
+
for (const [envFilePath, envName] of stored) {
|
|
93
|
+
if (fs.existsSync(envFilePath)) {
|
|
94
|
+
activeEnvironmentByEnvFile.set(envFilePath, envName);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
changed = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (changed) {
|
|
101
|
+
persistActiveEnvironments();
|
|
102
|
+
}
|
|
103
|
+
updateStatusBar();
|
|
104
|
+
}
|
|
105
|
+
function persistActiveEnvironments() {
|
|
106
|
+
if (!environmentMemento) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
void environmentMemento.update(ACTIVE_ENV_MEMENTO_KEY, Array.from(activeEnvironmentByEnvFile.entries()));
|
|
110
|
+
}
|
|
111
|
+
function getContextEnvFile() {
|
|
112
|
+
const activeEditor = vscode.window.activeTextEditor;
|
|
113
|
+
if (activeEditor && activeEditor.document.uri.scheme === 'file') {
|
|
114
|
+
return findEnvFileFromPath(activeEditor.document.uri.fsPath);
|
|
115
|
+
}
|
|
116
|
+
return findEnvFile();
|
|
117
|
+
}
|
|
118
|
+
function resolveEnvFilePath(pathOrSourceFile) {
|
|
119
|
+
if (!pathOrSourceFile) {
|
|
120
|
+
return getContextEnvFile();
|
|
121
|
+
}
|
|
122
|
+
const resolvedPath = path.resolve(pathOrSourceFile);
|
|
123
|
+
if (path.basename(resolvedPath) === environmentParser_1.ENV_FILENAME && fs.existsSync(resolvedPath)) {
|
|
124
|
+
return resolvedPath;
|
|
125
|
+
}
|
|
126
|
+
return findEnvFileFromPath(resolvedPath);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Finds the .nornenv file in the workspace
|
|
130
|
+
*/
|
|
131
|
+
function findEnvFile() {
|
|
132
|
+
const workspaceFolders = vscode.workspace.workspaceFolders;
|
|
133
|
+
if (!workspaceFolders) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
for (const folder of workspaceFolders) {
|
|
137
|
+
const envPath = path.join(folder.uri.fsPath, environmentParser_1.ENV_FILENAME);
|
|
138
|
+
if (fs.existsSync(envPath)) {
|
|
139
|
+
return envPath;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Finds .nornenv file relative to a specific file path (for CLI usage)
|
|
146
|
+
*/
|
|
147
|
+
function findEnvFileFromPath(filePath) {
|
|
148
|
+
return (0, environmentParser_1.findEnvFileFromPath)(filePath);
|
|
149
|
+
}
|
|
150
|
+
// parseEnvFile is now imported from ./environmentParser
|
|
151
|
+
/**
|
|
152
|
+
* Reads and parses the resolved .nornenv file.
|
|
153
|
+
* Accepts either:
|
|
154
|
+
* - a path to a .nornenv file, or
|
|
155
|
+
* - a source file/directory path (it will resolve nearest .nornenv), or
|
|
156
|
+
* - undefined (uses current editor context/workspace fallback).
|
|
157
|
+
*/
|
|
158
|
+
function loadEnvironmentConfig(pathOrSourceFile) {
|
|
159
|
+
const filePath = resolveEnvFilePath(pathOrSourceFile);
|
|
160
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const result = (0, environmentParser_1.loadAndResolveEnvFile)(filePath);
|
|
165
|
+
if (result.errors.length > 0 || result.secretErrors.length > 0) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return result.config;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function getEnvironmentSecretErrorDetails(pathOrSourceFile) {
|
|
175
|
+
const filePath = resolveEnvFilePath(pathOrSourceFile);
|
|
176
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const result = (0, environmentParser_1.loadAndResolveEnvFile)(filePath);
|
|
181
|
+
return result.secretErrors;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function getEnvironmentSecretErrors(pathOrSourceFile) {
|
|
188
|
+
return getEnvironmentSecretErrorDetails(pathOrSourceFile).map(e => e.message);
|
|
189
|
+
}
|
|
190
|
+
function getEnvironmentSecretNames(pathOrSourceFile) {
|
|
191
|
+
const filePath = resolveEnvFilePath(pathOrSourceFile);
|
|
192
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
193
|
+
return new Set();
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const result = (0, environmentParser_1.loadAndResolveEnvFile)(filePath);
|
|
197
|
+
if (result.errors.length > 0) {
|
|
198
|
+
return new Set();
|
|
199
|
+
}
|
|
200
|
+
return new Set(result.config.secretNames);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return new Set();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function getUnlockableKids(errors) {
|
|
207
|
+
const kids = new Set();
|
|
208
|
+
for (const err of errors) {
|
|
209
|
+
if ((err.code === 'missing-key' || err.code === 'decrypt-failed') && err.kid) {
|
|
210
|
+
kids.add(err.kid);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return Array.from(kids.values()).sort();
|
|
214
|
+
}
|
|
215
|
+
async function promptForMissingEnvironmentSecretKeys(pathOrSourceFile) {
|
|
216
|
+
const filePath = resolveEnvFilePath(pathOrSourceFile);
|
|
217
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
221
|
+
const secretErrors = getEnvironmentSecretErrorDetails(filePath);
|
|
222
|
+
const kids = getUnlockableKids(secretErrors);
|
|
223
|
+
if (kids.length === 0) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
for (const kid of kids) {
|
|
227
|
+
const value = await vscode.window.showInputBox({
|
|
228
|
+
prompt: `Enter shared key for kid '${kid}'`,
|
|
229
|
+
password: true,
|
|
230
|
+
ignoreFocusOut: true,
|
|
231
|
+
placeHolder: 'Paste shared key (it will be cached in the project .norn-cache)'
|
|
232
|
+
});
|
|
233
|
+
if (!value || value.trim() === '') {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
(0, keyStore_1.saveSecretKeyToCache)(kid, value.trim(), filePath);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return getUnlockableKids(getEnvironmentSecretErrorDetails(filePath)).length === 0;
|
|
240
|
+
}
|
|
241
|
+
function setEnvironmentSessionSecretKey(kid, key) {
|
|
242
|
+
(0, keyStore_1.setSessionSecretKey)(kid, key);
|
|
243
|
+
}
|
|
244
|
+
function clearEnvironmentSessionSecretKeys() {
|
|
245
|
+
(0, keyStore_1.clearSessionSecretKeys)();
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Returns the active environment for the `.nornenv` in scope for `pathOrSourceFile`,
|
|
249
|
+
* or for the current editor's context if omitted. Returns `undefined` when no `.nornenv`
|
|
250
|
+
* is in scope or when no env has been activated for that scope.
|
|
251
|
+
*/
|
|
252
|
+
function getActiveEnvironment(pathOrSourceFile) {
|
|
253
|
+
const envFilePath = resolveEnvFilePath(pathOrSourceFile);
|
|
254
|
+
if (!envFilePath) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
return activeEnvironmentByEnvFile.get(envFilePath);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Sets the active environment for the `.nornenv` in scope for `pathOrSourceFile`,
|
|
261
|
+
* or for the current editor's context if omitted. Persists the change to the workspace
|
|
262
|
+
* memento so it survives reload. Pass `undefined` to clear the selection for that scope.
|
|
263
|
+
*/
|
|
264
|
+
function setActiveEnvironment(envName, pathOrSourceFile) {
|
|
265
|
+
const envFilePath = resolveEnvFilePath(pathOrSourceFile);
|
|
266
|
+
if (!envFilePath) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const previous = activeEnvironmentByEnvFile.get(envFilePath);
|
|
270
|
+
if (envName === undefined) {
|
|
271
|
+
activeEnvironmentByEnvFile.delete(envFilePath);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
activeEnvironmentByEnvFile.set(envFilePath, envName);
|
|
275
|
+
}
|
|
276
|
+
persistActiveEnvironments();
|
|
277
|
+
updateStatusBar();
|
|
278
|
+
if (previous !== envName) {
|
|
279
|
+
activeEnvironmentEmitter.fire(envName);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Gets all variables for the current environment.
|
|
284
|
+
*
|
|
285
|
+
* Resolution: `common` ← ancestor templates/envs (left-to-right walk, right-most wins) ← self.
|
|
286
|
+
* Templates contribute their variables via `extends` but are never directly selectable.
|
|
287
|
+
*
|
|
288
|
+
* `envVariables` (passed to scope attachment) is the active env's **self-declared** vars only —
|
|
289
|
+
* not including ancestors. This keeps the runtime scope check meaningful (an env-section
|
|
290
|
+
* self-reference fires the scope diagnostic), while ancestor references are permitted
|
|
291
|
+
* because ancestor names appear in neither commonNames nor environmentNames.
|
|
292
|
+
*/
|
|
293
|
+
function getEnvironmentVariables(pathOrSourceFile) {
|
|
294
|
+
const config = loadEnvironmentConfig(pathOrSourceFile);
|
|
295
|
+
if (!config) {
|
|
296
|
+
return {};
|
|
297
|
+
}
|
|
298
|
+
let variables = { ...config.common };
|
|
299
|
+
let envVariables;
|
|
300
|
+
const activeForContext = getActiveEnvironment(pathOrSourceFile);
|
|
301
|
+
if (activeForContext) {
|
|
302
|
+
const env = config.environments.find(e => e.name === activeForContext);
|
|
303
|
+
if (env) {
|
|
304
|
+
envVariables = env.variables;
|
|
305
|
+
// resolveEffectiveEnvVariables returns common + ancestor chain + self merged
|
|
306
|
+
// with the correct precedence (right-most parent wins; self overrides all).
|
|
307
|
+
variables = (0, environmentParser_1.resolveEffectiveEnvVariables)(activeForContext, config);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return (0, environmentTemplates_1.attachEnvironmentTemplateScopes)(variables, config.common, envVariables);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Checks if the resolved .nornenv file has import errors.
|
|
314
|
+
* Returns the error messages, or an empty array if no errors.
|
|
315
|
+
*/
|
|
316
|
+
function getEnvironmentImportErrors(pathOrSourceFile) {
|
|
317
|
+
return getEnvironmentImportErrorDetails(pathOrSourceFile).map(e => e.message);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Returns detailed .nornenv import errors (message + file path + line).
|
|
321
|
+
*/
|
|
322
|
+
function getEnvironmentImportErrorDetails(pathOrSourceFile) {
|
|
323
|
+
const filePath = resolveEnvFilePath(pathOrSourceFile);
|
|
324
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
const result = (0, environmentParser_1.loadAndResolveEnvFile)(filePath);
|
|
329
|
+
return result.errors;
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Gets available environment names
|
|
337
|
+
*/
|
|
338
|
+
function getAvailableEnvironments(pathOrSourceFile) {
|
|
339
|
+
const filePath = resolveEnvFilePath(pathOrSourceFile);
|
|
340
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
const result = (0, environmentParser_1.loadAndResolveEnvFile)(filePath);
|
|
345
|
+
return result.config.environments.map(e => e.name);
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Creates and shows the status bar item
|
|
353
|
+
*/
|
|
354
|
+
function createStatusBarItem() {
|
|
355
|
+
statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
|
|
356
|
+
statusBarItem.command = 'norn.selectEnvironment';
|
|
357
|
+
updateStatusBar();
|
|
358
|
+
statusBarItem.show();
|
|
359
|
+
return statusBarItem;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Refreshes the status bar text based on current editor context.
|
|
363
|
+
*/
|
|
364
|
+
function refreshEnvironmentStatusBar() {
|
|
365
|
+
updateStatusBar();
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Updates the status bar item text
|
|
369
|
+
*/
|
|
370
|
+
function updateStatusBar() {
|
|
371
|
+
if (!statusBarItem) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const envFile = getContextEnvFile();
|
|
375
|
+
if (!envFile) {
|
|
376
|
+
statusBarItem.text = '$(globe) Norn: No Env';
|
|
377
|
+
statusBarItem.tooltip = 'No .nornenv file found';
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const activeForContext = activeEnvironmentByEnvFile.get(envFile);
|
|
381
|
+
if (activeForContext) {
|
|
382
|
+
statusBarItem.text = `$(globe) Norn: ${activeForContext}`;
|
|
383
|
+
statusBarItem.tooltip = `Active environment: ${activeForContext}\nScope: ${envFile}\nClick to change`;
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
statusBarItem.text = '$(globe) Norn: Select Env';
|
|
387
|
+
statusBarItem.tooltip = `No env selected for ${envFile}\nClick to select`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Shows the environment picker
|
|
392
|
+
*/
|
|
393
|
+
async function showEnvironmentPicker(pathOrSourceFile) {
|
|
394
|
+
const envFilePath = pathOrSourceFile ? resolveEnvFilePath(pathOrSourceFile) : getContextEnvFile();
|
|
395
|
+
const environments = envFilePath ? getAvailableEnvironments(envFilePath) : [];
|
|
396
|
+
if (environments.length === 0) {
|
|
397
|
+
const createFile = await vscode.window.showInformationMessage('No .nornenv file found. Would you like to create one?', 'Create .nornenv', 'Cancel');
|
|
398
|
+
if (createFile === 'Create .nornenv') {
|
|
399
|
+
await createEnvFileTemplate();
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const activeForScope = envFilePath ? activeEnvironmentByEnvFile.get(envFilePath) : undefined;
|
|
404
|
+
const items = [
|
|
405
|
+
{
|
|
406
|
+
label: '$(circle-slash) None',
|
|
407
|
+
description: 'Use only common variables',
|
|
408
|
+
picked: !activeForScope
|
|
409
|
+
},
|
|
410
|
+
...environments.map(env => ({
|
|
411
|
+
label: `$(server-environment) ${env}`,
|
|
412
|
+
description: env === activeForScope ? '(active)' : '',
|
|
413
|
+
picked: env === activeForScope
|
|
414
|
+
}))
|
|
415
|
+
];
|
|
416
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
417
|
+
placeHolder: 'Select environment',
|
|
418
|
+
title: 'Norn Environment'
|
|
419
|
+
});
|
|
420
|
+
if (selected) {
|
|
421
|
+
if (selected.label.includes('None')) {
|
|
422
|
+
setActiveEnvironment(undefined, envFilePath);
|
|
423
|
+
vscode.window.showInformationMessage('Norn: Environment cleared');
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
const envName = selected.label.replace('$(server-environment) ', '');
|
|
427
|
+
setActiveEnvironment(envName, envFilePath);
|
|
428
|
+
vscode.window.showInformationMessage(`Norn: Switched to "${envName}" environment`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Creates a template .nornenv file
|
|
434
|
+
*/
|
|
435
|
+
async function createEnvFileTemplate() {
|
|
436
|
+
const workspaceFolders = vscode.workspace.workspaceFolders;
|
|
437
|
+
if (!workspaceFolders) {
|
|
438
|
+
vscode.window.showErrorMessage('No workspace folder open');
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const template = `# Norn Environment Configuration
|
|
442
|
+
# Common variables are available in all environments
|
|
443
|
+
|
|
444
|
+
var timeout = 30000
|
|
445
|
+
var version = v1
|
|
446
|
+
|
|
447
|
+
# Development environment
|
|
448
|
+
[env:dev]
|
|
449
|
+
var baseUrl = http://localhost:3000
|
|
450
|
+
var apiKey = dev-key-123
|
|
451
|
+
|
|
452
|
+
# Staging environment
|
|
453
|
+
[env:staging]
|
|
454
|
+
var baseUrl = https://staging.api.example.com
|
|
455
|
+
var apiKey = staging-key-456
|
|
456
|
+
|
|
457
|
+
# Production environment
|
|
458
|
+
[env:prod]
|
|
459
|
+
var baseUrl = https://api.example.com
|
|
460
|
+
var apiKey = prod-key-789
|
|
461
|
+
`;
|
|
462
|
+
const envPath = path.join(workspaceFolders[0].uri.fsPath, environmentParser_1.ENV_FILENAME);
|
|
463
|
+
fs.writeFileSync(envPath, template, 'utf-8');
|
|
464
|
+
const doc = await vscode.workspace.openTextDocument(envPath);
|
|
465
|
+
await vscode.window.showTextDocument(doc);
|
|
466
|
+
updateStatusBar();
|
|
467
|
+
vscode.window.showInformationMessage('Created .nornenv file');
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Disposes the status bar item
|
|
471
|
+
*/
|
|
472
|
+
function disposeStatusBar() {
|
|
473
|
+
if (statusBarItem) {
|
|
474
|
+
statusBarItem.dispose();
|
|
475
|
+
statusBarItem = undefined;
|
|
476
|
+
}
|
|
477
|
+
if (coverageStatusBarItem) {
|
|
478
|
+
coverageStatusBarItem.dispose();
|
|
479
|
+
coverageStatusBarItem = undefined;
|
|
480
|
+
}
|
|
481
|
+
activeEnvironmentEmitter.dispose();
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Creates the coverage status bar item
|
|
485
|
+
*/
|
|
486
|
+
function createCoverageStatusBarItem() {
|
|
487
|
+
coverageStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 99);
|
|
488
|
+
coverageStatusBarItem.command = 'norn.showCoverage';
|
|
489
|
+
coverageStatusBarItem.hide(); // Hidden until we know there's swagger
|
|
490
|
+
return coverageStatusBarItem;
|
|
491
|
+
}
|
|
492
|
+
function getActiveNornapiPath() {
|
|
493
|
+
const activeEditor = vscode.window.activeTextEditor;
|
|
494
|
+
if (!activeEditor) {
|
|
495
|
+
return undefined;
|
|
496
|
+
}
|
|
497
|
+
return activeEditor.document.languageId === 'nornapi'
|
|
498
|
+
? activeEditor.document.uri.fsPath
|
|
499
|
+
: undefined;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Refresh coverage status bar command context for the current active editor.
|
|
503
|
+
*/
|
|
504
|
+
function refreshCoverageStatusBarContext() {
|
|
505
|
+
if (!coverageStatusBarItem || !hasCoverageData) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const activeNornapiPath = getActiveNornapiPath();
|
|
509
|
+
coverageStatusBarItem.command = {
|
|
510
|
+
command: 'norn.showCoverage',
|
|
511
|
+
title: 'Show Coverage',
|
|
512
|
+
arguments: activeNornapiPath ? [activeNornapiPath] : []
|
|
513
|
+
};
|
|
514
|
+
coverageStatusBarItem.text = '$(graph) Coverage';
|
|
515
|
+
coverageStatusBarItem.tooltip = activeNornapiPath
|
|
516
|
+
? 'Show API coverage for this .nornapi file (includes this folder and subfolders only).'
|
|
517
|
+
: 'Show API coverage';
|
|
518
|
+
coverageStatusBarItem.show();
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Updates the coverage status bar display
|
|
522
|
+
*/
|
|
523
|
+
function updateCoverageStatusBar(hasSwagger, percentage, total, covered) {
|
|
524
|
+
if (!coverageStatusBarItem) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (!hasSwagger) {
|
|
528
|
+
hasCoverageData = false;
|
|
529
|
+
coverageStatusBarItem.hide();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
hasCoverageData = true;
|
|
533
|
+
void percentage;
|
|
534
|
+
void total;
|
|
535
|
+
void covered;
|
|
536
|
+
refreshCoverageStatusBarContext();
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Get the coverage status bar item (for external updates)
|
|
540
|
+
*/
|
|
541
|
+
function getCoverageStatusBarItem() {
|
|
542
|
+
return coverageStatusBarItem;
|
|
543
|
+
}
|
|
544
|
+
//# sourceMappingURL=environmentProvider.js.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.attachEnvironmentTemplateScopes = attachEnvironmentTemplateScopes;
|
|
4
|
+
exports.hasEnvironmentTemplate = hasEnvironmentTemplate;
|
|
5
|
+
exports.resolveEnvironmentTemplateValue = resolveEnvironmentTemplateValue;
|
|
6
|
+
exports.collectResolvedEnvironmentSecretValues = collectResolvedEnvironmentSecretValues;
|
|
7
|
+
const TEMPLATE_TOKEN_REGEX = /\{\{([^{}]+)\}\}/g;
|
|
8
|
+
const ENV_REFERENCE_REGEX = /^(?:\$env\.)?([a-zA-Z_][a-zA-Z0-9_]*)$/;
|
|
9
|
+
const ENV_TEMPLATE_SCOPES_KEY = '$nornEnvTemplateScopes';
|
|
10
|
+
function attachEnvironmentTemplateScopes(variables, commonVariables, environmentVariables) {
|
|
11
|
+
Object.defineProperty(variables, ENV_TEMPLATE_SCOPES_KEY, {
|
|
12
|
+
value: {
|
|
13
|
+
commonNames: new Set(Object.keys(commonVariables)),
|
|
14
|
+
environmentNames: new Set(Object.keys(environmentVariables ?? {}))
|
|
15
|
+
},
|
|
16
|
+
enumerable: false,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: false
|
|
19
|
+
});
|
|
20
|
+
return variables;
|
|
21
|
+
}
|
|
22
|
+
function getEnvironmentTemplateScopes(variables) {
|
|
23
|
+
const value = variables[ENV_TEMPLATE_SCOPES_KEY];
|
|
24
|
+
if (!value || typeof value !== 'object') {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const candidate = value;
|
|
28
|
+
if (!(candidate.commonNames instanceof Set) || !(candidate.environmentNames instanceof Set)) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
return candidate;
|
|
32
|
+
}
|
|
33
|
+
function valueToTemplateString(value) {
|
|
34
|
+
if (value === undefined || value === null) {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === 'object') {
|
|
38
|
+
return JSON.stringify(value);
|
|
39
|
+
}
|
|
40
|
+
return String(value);
|
|
41
|
+
}
|
|
42
|
+
function parseTemplateReference(rawReference) {
|
|
43
|
+
const trimmed = rawReference.trim();
|
|
44
|
+
const match = trimmed.match(ENV_REFERENCE_REGEX);
|
|
45
|
+
return match?.[1];
|
|
46
|
+
}
|
|
47
|
+
function hasEnvironmentTemplate(value) {
|
|
48
|
+
return typeof value === 'string' && /\{\{[^{}]+\}\}/.test(value);
|
|
49
|
+
}
|
|
50
|
+
function resolveEnvironmentTemplateValue(variableName, variables, secretNames = new Set()) {
|
|
51
|
+
const resolving = new Set();
|
|
52
|
+
const cache = new Map();
|
|
53
|
+
const scopes = getEnvironmentTemplateScopes(variables);
|
|
54
|
+
const resolveVariable = (name) => {
|
|
55
|
+
const cached = cache.get(name);
|
|
56
|
+
if (cached) {
|
|
57
|
+
return cached;
|
|
58
|
+
}
|
|
59
|
+
if (resolving.has(name)) {
|
|
60
|
+
const cycle = [...resolving, name].join(' -> ');
|
|
61
|
+
return {
|
|
62
|
+
value: `{{${name}}}`,
|
|
63
|
+
secret: secretNames.has(name),
|
|
64
|
+
dependencies: new Set([name]),
|
|
65
|
+
errors: [{
|
|
66
|
+
kind: 'cycle',
|
|
67
|
+
variableName: name,
|
|
68
|
+
reference: name,
|
|
69
|
+
message: `Circular .nornenv template reference detected: ${cycle}`
|
|
70
|
+
}]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (!Object.prototype.hasOwnProperty.call(variables, name)) {
|
|
74
|
+
return {
|
|
75
|
+
value: `{{${name}}}`,
|
|
76
|
+
secret: false,
|
|
77
|
+
dependencies: new Set([name]),
|
|
78
|
+
errors: [{
|
|
79
|
+
kind: 'unresolved',
|
|
80
|
+
variableName: name,
|
|
81
|
+
reference: name,
|
|
82
|
+
message: `.nornenv template reference '${name}' was not found in the active environment.`
|
|
83
|
+
}]
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
resolving.add(name);
|
|
87
|
+
const rawValue = valueToTemplateString(variables[name]);
|
|
88
|
+
const dependencies = new Set();
|
|
89
|
+
const errors = [];
|
|
90
|
+
let secret = secretNames.has(name);
|
|
91
|
+
const value = rawValue.replace(TEMPLATE_TOKEN_REGEX, (token, rawReference) => {
|
|
92
|
+
const referenceName = parseTemplateReference(rawReference);
|
|
93
|
+
if (!referenceName) {
|
|
94
|
+
errors.push({
|
|
95
|
+
kind: 'unsupported',
|
|
96
|
+
variableName: name,
|
|
97
|
+
reference: rawReference.trim(),
|
|
98
|
+
message: `.nornenv template reference '${rawReference.trim()}' is not supported. Use {{name}} or {{$env.name}}.`
|
|
99
|
+
});
|
|
100
|
+
return token;
|
|
101
|
+
}
|
|
102
|
+
const resolved = resolveVariable(referenceName);
|
|
103
|
+
dependencies.add(referenceName);
|
|
104
|
+
for (const dependency of resolved.dependencies) {
|
|
105
|
+
dependencies.add(dependency);
|
|
106
|
+
}
|
|
107
|
+
if (scopes?.environmentNames.has(name) && scopes.environmentNames.has(referenceName)) {
|
|
108
|
+
errors.push({
|
|
109
|
+
kind: 'scope',
|
|
110
|
+
variableName: name,
|
|
111
|
+
reference: referenceName,
|
|
112
|
+
message: `.nornenv template reference '${referenceName}' is not in scope. Values inside [env:...] sections can reference common variables and inherited ancestor variables only.`
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (resolved.secret) {
|
|
116
|
+
secret = true;
|
|
117
|
+
}
|
|
118
|
+
errors.push(...resolved.errors);
|
|
119
|
+
return resolved.value;
|
|
120
|
+
});
|
|
121
|
+
resolving.delete(name);
|
|
122
|
+
const resolution = {
|
|
123
|
+
value,
|
|
124
|
+
secret,
|
|
125
|
+
dependencies,
|
|
126
|
+
errors
|
|
127
|
+
};
|
|
128
|
+
cache.set(name, resolution);
|
|
129
|
+
return resolution;
|
|
130
|
+
};
|
|
131
|
+
return resolveVariable(variableName);
|
|
132
|
+
}
|
|
133
|
+
function collectResolvedEnvironmentSecretValues(variables, secretNames) {
|
|
134
|
+
const values = new Map();
|
|
135
|
+
for (const name of secretNames) {
|
|
136
|
+
if (!Object.prototype.hasOwnProperty.call(variables, name)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const resolved = resolveEnvironmentTemplateValue(name, variables, secretNames);
|
|
140
|
+
if (resolved.errors.length === 0) {
|
|
141
|
+
values.set(name, resolved.value);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return values;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=environmentTemplates.js.map
|