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,415 @@
|
|
|
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.ensureCliSecretsUnlocked = ensureCliSecretsUnlocked;
|
|
37
|
+
exports.handleSecretsCommand = handleSecretsCommand;
|
|
38
|
+
exports.printSecretResolutionErrors = printSecretResolutionErrors;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const readline = __importStar(require("readline"));
|
|
42
|
+
const process_1 = require("process");
|
|
43
|
+
const environmentParser_1 = require("../environmentParser");
|
|
44
|
+
const crypto_1 = require("./crypto");
|
|
45
|
+
const envFileSecrets_1 = require("./envFileSecrets");
|
|
46
|
+
const keyStore_1 = require("./keyStore");
|
|
47
|
+
function getFlagValue(args, flag, shortFlag) {
|
|
48
|
+
const longIndex = args.indexOf(flag);
|
|
49
|
+
if (longIndex >= 0 && longIndex + 1 < args.length) {
|
|
50
|
+
return args[longIndex + 1];
|
|
51
|
+
}
|
|
52
|
+
if (shortFlag) {
|
|
53
|
+
const shortIndex = args.indexOf(shortFlag);
|
|
54
|
+
if (shortIndex >= 0 && shortIndex + 1 < args.length) {
|
|
55
|
+
return args[shortIndex + 1];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
function hasFlag(args, flag) {
|
|
61
|
+
return args.includes(flag);
|
|
62
|
+
}
|
|
63
|
+
async function promptLine(question) {
|
|
64
|
+
if (!process.stdin.isTTY) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const rl = readline.createInterface({ input: process_1.stdin, output: process_1.stdout });
|
|
68
|
+
try {
|
|
69
|
+
const answer = await new Promise((resolve) => {
|
|
70
|
+
rl.question(question, resolve);
|
|
71
|
+
});
|
|
72
|
+
const trimmed = answer.trim();
|
|
73
|
+
return trimmed === '' ? undefined : trimmed;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
rl.close();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function uniqueUnlockableKids(errors) {
|
|
80
|
+
const kids = new Set();
|
|
81
|
+
for (const err of errors) {
|
|
82
|
+
if ((err.code === 'missing-key' || err.code === 'decrypt-failed') && err.kid) {
|
|
83
|
+
kids.add(err.kid);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return Array.from(kids).sort();
|
|
87
|
+
}
|
|
88
|
+
function formatSecretError(err, envFilePath) {
|
|
89
|
+
if (!envFilePath) {
|
|
90
|
+
return `${err.message}`;
|
|
91
|
+
}
|
|
92
|
+
const relative = path.relative(path.dirname(envFilePath), err.filePath);
|
|
93
|
+
const fileLabel = relative && relative !== '' ? relative : path.basename(err.filePath);
|
|
94
|
+
const lineLabel = err.line >= 0 ? `${fileLabel}:${err.line + 1}` : fileLabel;
|
|
95
|
+
return `${lineLabel} - ${err.message}`;
|
|
96
|
+
}
|
|
97
|
+
async function ensureKeyForKid(kid, contextPath) {
|
|
98
|
+
const existing = (0, keyStore_1.resolveSecretKey)(kid, contextPath);
|
|
99
|
+
if (existing) {
|
|
100
|
+
return existing;
|
|
101
|
+
}
|
|
102
|
+
const entered = await promptLine(`Enter shared key for kid '${kid}': `);
|
|
103
|
+
if (!entered) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
(0, keyStore_1.saveSecretKeyToCache)(kid, entered, contextPath);
|
|
107
|
+
return entered;
|
|
108
|
+
}
|
|
109
|
+
async function ensureCliSecretsUnlocked(targetPath) {
|
|
110
|
+
const envFilePath = (0, environmentParser_1.findEnvFileFromPath)(targetPath);
|
|
111
|
+
if (!envFilePath) {
|
|
112
|
+
return { ok: true, errors: [] };
|
|
113
|
+
}
|
|
114
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
115
|
+
const result = (0, environmentParser_1.loadAndResolveEnvFile)(envFilePath);
|
|
116
|
+
const unlockableKids = uniqueUnlockableKids(result.secretErrors);
|
|
117
|
+
if (unlockableKids.length === 0) {
|
|
118
|
+
return { ok: result.secretErrors.length === 0, envFilePath, errors: result.secretErrors };
|
|
119
|
+
}
|
|
120
|
+
for (const kid of unlockableKids) {
|
|
121
|
+
const key = await ensureKeyForKid(kid, envFilePath);
|
|
122
|
+
if (!key) {
|
|
123
|
+
return { ok: false, envFilePath, errors: result.secretErrors };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const finalResult = (0, environmentParser_1.loadAndResolveEnvFile)(envFilePath);
|
|
128
|
+
return { ok: finalResult.secretErrors.length === 0, envFilePath, errors: finalResult.secretErrors };
|
|
129
|
+
}
|
|
130
|
+
function printSecretsHelp() {
|
|
131
|
+
console.log(`
|
|
132
|
+
Norn Secrets Commands
|
|
133
|
+
|
|
134
|
+
Usage:
|
|
135
|
+
norn secrets keygen --name <kid>
|
|
136
|
+
norn secrets import-key --kid <kid> [--key <value>]
|
|
137
|
+
norn secrets encrypt --file <.nornenv> --var <name> [--env <env>] [--kid <kid>]
|
|
138
|
+
norn secrets rotate --file <.nornenv> --var <name> [--env <env>] [--kid <kid>] [--value <plaintext>]
|
|
139
|
+
norn secrets rekey [path] [--kid <newKid>]
|
|
140
|
+
norn secrets audit [path]
|
|
141
|
+
norn secrets keys
|
|
142
|
+
norn secrets forget --kid <kid>
|
|
143
|
+
|
|
144
|
+
Notes:
|
|
145
|
+
- Secrets are stored as ENC[NORN_AGE_V1:kid=<id>:<payload>] in .nornenv.
|
|
146
|
+
- Keys are cached locally in the project .norn-cache/secret-keys.json (gitignored).
|
|
147
|
+
- norn run auto-prompts once for missing kids when interactive.
|
|
148
|
+
`);
|
|
149
|
+
}
|
|
150
|
+
async function handleKeygen(args) {
|
|
151
|
+
const kid = getFlagValue(args, '--name', '-n');
|
|
152
|
+
if (!kid) {
|
|
153
|
+
console.error(`Error: Missing --name for keygen.`);
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
const key = (0, crypto_1.generateSharedSecretKey)();
|
|
157
|
+
(0, keyStore_1.saveSecretKeyToCache)(kid, key, process.cwd());
|
|
158
|
+
console.log(`Generated shared key for kid '${kid}'.`);
|
|
159
|
+
console.log(`Store this key in your team vault now:`);
|
|
160
|
+
console.log(key);
|
|
161
|
+
console.log(`Saved to local cache for current workspace.`);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
async function handleImportKey(args) {
|
|
165
|
+
const kid = getFlagValue(args, '--kid', '-k');
|
|
166
|
+
if (!kid) {
|
|
167
|
+
console.error(`Error: Missing --kid for import-key.`);
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
const directKey = getFlagValue(args, '--key');
|
|
171
|
+
const key = directKey ?? await promptLine(`Enter shared key for kid '${kid}': `);
|
|
172
|
+
if (!key) {
|
|
173
|
+
console.error(`Error: No key provided.`);
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
(0, keyStore_1.saveSecretKeyToCache)(kid, key, process.cwd());
|
|
177
|
+
console.log(`Saved key for kid '${kid}' to local cache.`);
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
async function handleEncrypt(args) {
|
|
181
|
+
const filePath = getFlagValue(args, '--file', '-f');
|
|
182
|
+
const variableName = getFlagValue(args, '--var');
|
|
183
|
+
const envName = getFlagValue(args, '--env');
|
|
184
|
+
const explicitKid = getFlagValue(args, '--kid');
|
|
185
|
+
if (!filePath || !variableName) {
|
|
186
|
+
console.error(`Error: encrypt requires --file and --var.`);
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
const absoluteFilePath = path.resolve(filePath);
|
|
190
|
+
const { content, secret } = (0, envFileSecrets_1.loadSecretLine)(absoluteFilePath, variableName, envName);
|
|
191
|
+
if (secret.encrypted) {
|
|
192
|
+
console.error(`Error: Secret '${variableName}' is already encrypted. Use rotate instead.`);
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
195
|
+
const kid = explicitKid;
|
|
196
|
+
if (!kid) {
|
|
197
|
+
console.error(`Error: --kid is required when encrypting plaintext secret '${variableName}'.`);
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
const key = await ensureKeyForKid(kid, absoluteFilePath);
|
|
201
|
+
if (!key) {
|
|
202
|
+
console.error(`Error: Missing key for kid '${kid}'.`);
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
const encryptedValue = (0, crypto_1.encryptSecretValue)(secret.value, key, kid);
|
|
206
|
+
const updatedContent = (0, envFileSecrets_1.updateSecretLineValue)(content, secret.lineNumber, encryptedValue);
|
|
207
|
+
(0, envFileSecrets_1.writeSecretLine)(absoluteFilePath, updatedContent);
|
|
208
|
+
console.log(`Encrypted secret '${variableName}' in ${absoluteFilePath}:${secret.lineNumber + 1}`);
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
async function handleRotate(args) {
|
|
212
|
+
const filePath = getFlagValue(args, '--file', '-f');
|
|
213
|
+
const variableName = getFlagValue(args, '--var');
|
|
214
|
+
const envName = getFlagValue(args, '--env');
|
|
215
|
+
const explicitKid = getFlagValue(args, '--kid');
|
|
216
|
+
const explicitValue = getFlagValue(args, '--value');
|
|
217
|
+
if (!filePath || !variableName) {
|
|
218
|
+
console.error(`Error: rotate requires --file and --var.`);
|
|
219
|
+
return 1;
|
|
220
|
+
}
|
|
221
|
+
const absoluteFilePath = path.resolve(filePath);
|
|
222
|
+
const { content, secret } = (0, envFileSecrets_1.loadSecretLine)(absoluteFilePath, variableName, envName);
|
|
223
|
+
let kid = explicitKid;
|
|
224
|
+
if (!kid && secret.encrypted) {
|
|
225
|
+
const parsed = (0, crypto_1.parseEncryptedSecretValue)(secret.value);
|
|
226
|
+
if (parsed.ok) {
|
|
227
|
+
kid = parsed.parsed.kid;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (!kid) {
|
|
231
|
+
console.error(`Error: Could not determine kid for '${variableName}'. Pass --kid.`);
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
234
|
+
const nextPlaintext = explicitValue ?? await promptLine(`Enter new plaintext value for '${variableName}': `);
|
|
235
|
+
if (nextPlaintext === undefined) {
|
|
236
|
+
console.error(`Error: No replacement value provided.`);
|
|
237
|
+
return 1;
|
|
238
|
+
}
|
|
239
|
+
const key = await ensureKeyForKid(kid, absoluteFilePath);
|
|
240
|
+
if (!key) {
|
|
241
|
+
console.error(`Error: Missing key for kid '${kid}'.`);
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
const encryptedValue = (0, crypto_1.encryptSecretValue)(nextPlaintext, key, kid);
|
|
245
|
+
const updatedContent = (0, envFileSecrets_1.updateSecretLineValue)(content, secret.lineNumber, encryptedValue);
|
|
246
|
+
(0, envFileSecrets_1.writeSecretLine)(absoluteFilePath, updatedContent);
|
|
247
|
+
console.log(`Rotated secret '${variableName}' in ${absoluteFilePath}:${secret.lineNumber + 1}`);
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
async function handleRekey(args) {
|
|
251
|
+
const positional = args.filter(arg => !arg.startsWith('-'));
|
|
252
|
+
const targetPath = positional[0] ? path.resolve(positional[0]) : process.cwd();
|
|
253
|
+
const targetKid = getFlagValue(args, '--kid');
|
|
254
|
+
const files = (0, envFileSecrets_1.discoverNornenvFiles)(targetPath);
|
|
255
|
+
if (files.length === 0) {
|
|
256
|
+
console.log(`No .nornenv files found under ${targetPath}`);
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
let updatedFiles = 0;
|
|
260
|
+
let updatedSecrets = 0;
|
|
261
|
+
for (const filePath of files) {
|
|
262
|
+
const original = fs.readFileSync(filePath, 'utf8');
|
|
263
|
+
const secretLines = (0, envFileSecrets_1.extractSecretLines)(original, filePath);
|
|
264
|
+
if (secretLines.length === 0) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const replacements = new Map();
|
|
268
|
+
for (const secret of secretLines) {
|
|
269
|
+
if (!secret.encrypted) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const parsed = (0, crypto_1.parseEncryptedSecretValue)(secret.value);
|
|
273
|
+
if (!parsed.ok) {
|
|
274
|
+
console.error(`Skipping malformed secret at ${filePath}:${secret.lineNumber + 1}: ${parsed.error}`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const sourceKid = parsed.parsed.kid;
|
|
278
|
+
const decryptKey = await ensureKeyForKid(sourceKid, filePath);
|
|
279
|
+
if (!decryptKey) {
|
|
280
|
+
console.error(`Missing key for source kid '${sourceKid}' (${filePath}:${secret.lineNumber + 1})`);
|
|
281
|
+
return 1;
|
|
282
|
+
}
|
|
283
|
+
let plaintext;
|
|
284
|
+
try {
|
|
285
|
+
plaintext = (0, crypto_1.decryptSecretValue)(secret.value, decryptKey);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
289
|
+
console.error(`Failed to decrypt ${filePath}:${secret.lineNumber + 1} (${sourceKid}): ${message}`);
|
|
290
|
+
return 1;
|
|
291
|
+
}
|
|
292
|
+
const destinationKid = targetKid ?? sourceKid;
|
|
293
|
+
const encryptKey = destinationKid === sourceKid ? decryptKey : await ensureKeyForKid(destinationKid, filePath);
|
|
294
|
+
if (!encryptKey) {
|
|
295
|
+
console.error(`Missing key for destination kid '${destinationKid}'`);
|
|
296
|
+
return 1;
|
|
297
|
+
}
|
|
298
|
+
replacements.set(secret.lineNumber, (0, crypto_1.encryptSecretValue)(plaintext, encryptKey, destinationKid));
|
|
299
|
+
}
|
|
300
|
+
if (replacements.size === 0) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
let updated = original;
|
|
304
|
+
const orderedLines = Array.from(replacements.keys()).sort((a, b) => a - b);
|
|
305
|
+
for (const lineNumber of orderedLines) {
|
|
306
|
+
const value = replacements.get(lineNumber);
|
|
307
|
+
if (!value) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
updated = (0, envFileSecrets_1.updateSecretLineValue)(updated, lineNumber, value);
|
|
311
|
+
updatedSecrets += 1;
|
|
312
|
+
}
|
|
313
|
+
if (updated !== original) {
|
|
314
|
+
fs.writeFileSync(filePath, updated, 'utf8');
|
|
315
|
+
updatedFiles += 1;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
console.log(`Rekey complete. Updated ${updatedSecrets} secrets across ${updatedFiles} file(s).`);
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
321
|
+
async function handleAudit(args) {
|
|
322
|
+
const positional = args.filter(arg => !arg.startsWith('-'));
|
|
323
|
+
const targetPath = positional[0] ? path.resolve(positional[0]) : process.cwd();
|
|
324
|
+
const files = (0, envFileSecrets_1.discoverNornenvFiles)(targetPath);
|
|
325
|
+
if (files.length === 0) {
|
|
326
|
+
console.log(`No .nornenv files found under ${targetPath}`);
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
const issues = [];
|
|
330
|
+
for (const filePath of files) {
|
|
331
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
332
|
+
const secrets = (0, envFileSecrets_1.extractSecretLines)(content, filePath);
|
|
333
|
+
for (const secret of secrets) {
|
|
334
|
+
if (!secret.encrypted) {
|
|
335
|
+
issues.push(`${filePath}:${secret.lineNumber + 1} secret '${secret.name}' is plaintext.`);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const parsed = (0, crypto_1.parseEncryptedSecretValue)(secret.value);
|
|
339
|
+
if (!parsed.ok) {
|
|
340
|
+
issues.push(`${filePath}:${secret.lineNumber + 1} malformed encrypted value (${parsed.error}).`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (issues.length === 0) {
|
|
345
|
+
console.log(`Audit passed. All secret declarations are encrypted.`);
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
console.error(`Audit failed with ${issues.length} issue(s):`);
|
|
349
|
+
for (const issue of issues) {
|
|
350
|
+
console.error(` - ${issue}`);
|
|
351
|
+
}
|
|
352
|
+
return 1;
|
|
353
|
+
}
|
|
354
|
+
async function handleKeys() {
|
|
355
|
+
const keys = (0, keyStore_1.listCachedSecretKeyIds)(process.cwd());
|
|
356
|
+
if (keys.length === 0) {
|
|
357
|
+
console.log('No cached keys found for this workspace.');
|
|
358
|
+
return 0;
|
|
359
|
+
}
|
|
360
|
+
console.log('Cached key ids:');
|
|
361
|
+
for (const kid of keys) {
|
|
362
|
+
console.log(` - ${kid}`);
|
|
363
|
+
}
|
|
364
|
+
return 0;
|
|
365
|
+
}
|
|
366
|
+
async function handleForget(args) {
|
|
367
|
+
const kid = getFlagValue(args, '--kid', '-k');
|
|
368
|
+
if (!kid) {
|
|
369
|
+
console.error(`Error: forget requires --kid.`);
|
|
370
|
+
return 1;
|
|
371
|
+
}
|
|
372
|
+
const removed = (0, keyStore_1.removeSecretKeyFromCache)(kid, process.cwd());
|
|
373
|
+
if (!removed) {
|
|
374
|
+
console.log(`No cached key found for '${kid}'.`);
|
|
375
|
+
return 0;
|
|
376
|
+
}
|
|
377
|
+
console.log(`Removed cached key '${kid}'.`);
|
|
378
|
+
return 0;
|
|
379
|
+
}
|
|
380
|
+
async function handleSecretsCommand(args) {
|
|
381
|
+
const [subcommand, ...rest] = args;
|
|
382
|
+
switch (subcommand) {
|
|
383
|
+
case 'keygen':
|
|
384
|
+
return handleKeygen(rest);
|
|
385
|
+
case 'import-key':
|
|
386
|
+
return handleImportKey(rest);
|
|
387
|
+
case 'encrypt':
|
|
388
|
+
return handleEncrypt(rest);
|
|
389
|
+
case 'rotate':
|
|
390
|
+
return handleRotate(rest);
|
|
391
|
+
case 'rekey':
|
|
392
|
+
return handleRekey(rest);
|
|
393
|
+
case 'audit':
|
|
394
|
+
return handleAudit(rest);
|
|
395
|
+
case 'keys':
|
|
396
|
+
return handleKeys();
|
|
397
|
+
case 'forget':
|
|
398
|
+
return handleForget(rest);
|
|
399
|
+
case '--help':
|
|
400
|
+
case '-h':
|
|
401
|
+
case undefined:
|
|
402
|
+
printSecretsHelp();
|
|
403
|
+
return 0;
|
|
404
|
+
default:
|
|
405
|
+
console.error(`Unknown secrets command: ${subcommand}`);
|
|
406
|
+
printSecretsHelp();
|
|
407
|
+
return 1;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function printSecretResolutionErrors(errors, envFilePath) {
|
|
411
|
+
for (const err of errors) {
|
|
412
|
+
console.error(`Error: ${formatSecretError(err, envFilePath)}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
//# sourceMappingURL=cliSecrets.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
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.ENCRYPTED_SECRET_VERSION = exports.ENCRYPTED_SECRET_SUFFIX = exports.ENCRYPTED_SECRET_PREFIX = void 0;
|
|
37
|
+
exports.generateSharedSecretKey = generateSharedSecretKey;
|
|
38
|
+
exports.isEncryptedSecretValue = isEncryptedSecretValue;
|
|
39
|
+
exports.parseEncryptedSecretValue = parseEncryptedSecretValue;
|
|
40
|
+
exports.encryptSecretValue = encryptSecretValue;
|
|
41
|
+
exports.decryptSecretValue = decryptSecretValue;
|
|
42
|
+
const crypto = __importStar(require("crypto"));
|
|
43
|
+
exports.ENCRYPTED_SECRET_PREFIX = 'ENC[';
|
|
44
|
+
exports.ENCRYPTED_SECRET_SUFFIX = ']';
|
|
45
|
+
exports.ENCRYPTED_SECRET_VERSION = 'NORN_AGE_V1';
|
|
46
|
+
const encryptedSecretRegex = /^ENC\[([A-Z0-9_]+):kid=([a-zA-Z0-9._-]+):([A-Za-z0-9_-]+)\]$/;
|
|
47
|
+
function deriveEncryptionKey(sharedKey) {
|
|
48
|
+
return crypto.createHash('sha256').update(sharedKey, 'utf8').digest();
|
|
49
|
+
}
|
|
50
|
+
function generateSharedSecretKey() {
|
|
51
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
52
|
+
}
|
|
53
|
+
function isEncryptedSecretValue(value) {
|
|
54
|
+
return value.trim().startsWith(exports.ENCRYPTED_SECRET_PREFIX) && value.trim().endsWith(exports.ENCRYPTED_SECRET_SUFFIX);
|
|
55
|
+
}
|
|
56
|
+
function parseEncryptedSecretValue(value) {
|
|
57
|
+
const trimmed = value.trim();
|
|
58
|
+
const match = trimmed.match(encryptedSecretRegex);
|
|
59
|
+
if (!match) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
error: `Invalid encrypted secret format. Expected: ENC[${exports.ENCRYPTED_SECRET_VERSION}:kid=<id>:<payload>]`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
parsed: {
|
|
68
|
+
version: match[1],
|
|
69
|
+
kid: match[2],
|
|
70
|
+
payload: match[3]
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function encryptSecretValue(plaintext, sharedKey, kid) {
|
|
75
|
+
const key = deriveEncryptionKey(sharedKey);
|
|
76
|
+
const iv = crypto.randomBytes(12);
|
|
77
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
78
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
79
|
+
const authTag = cipher.getAuthTag();
|
|
80
|
+
const payload = Buffer.concat([iv, authTag, ciphertext]).toString('base64url');
|
|
81
|
+
return `ENC[${exports.ENCRYPTED_SECRET_VERSION}:kid=${kid}:${payload}]`;
|
|
82
|
+
}
|
|
83
|
+
function decryptSecretValue(encryptedValue, sharedKey) {
|
|
84
|
+
const parsedResult = parseEncryptedSecretValue(encryptedValue);
|
|
85
|
+
if (!parsedResult.ok) {
|
|
86
|
+
throw new Error(parsedResult.error);
|
|
87
|
+
}
|
|
88
|
+
const { version, payload } = parsedResult.parsed;
|
|
89
|
+
if (version !== exports.ENCRYPTED_SECRET_VERSION) {
|
|
90
|
+
throw new Error(`Unsupported encrypted secret version '${version}'. Expected '${exports.ENCRYPTED_SECRET_VERSION}'.`);
|
|
91
|
+
}
|
|
92
|
+
const raw = Buffer.from(payload, 'base64url');
|
|
93
|
+
if (raw.length < 28) {
|
|
94
|
+
throw new Error('Encrypted payload is too short.');
|
|
95
|
+
}
|
|
96
|
+
const iv = raw.subarray(0, 12);
|
|
97
|
+
const authTag = raw.subarray(12, 28);
|
|
98
|
+
const ciphertext = raw.subarray(28);
|
|
99
|
+
const key = deriveEncryptionKey(sharedKey);
|
|
100
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
101
|
+
decipher.setAuthTag(authTag);
|
|
102
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
103
|
+
return decrypted.toString('utf8');
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1,177 @@
|
|
|
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.isNornenvFilePath = isNornenvFilePath;
|
|
37
|
+
exports.isNornenvDocumentLike = isNornenvDocumentLike;
|
|
38
|
+
exports.extractSecretLines = extractSecretLines;
|
|
39
|
+
exports.updateSecretLineValue = updateSecretLineValue;
|
|
40
|
+
exports.findSecretLine = findSecretLine;
|
|
41
|
+
exports.loadSecretLine = loadSecretLine;
|
|
42
|
+
exports.writeSecretLine = writeSecretLine;
|
|
43
|
+
exports.discoverNornenvFiles = discoverNornenvFiles;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const crypto_1 = require("./crypto");
|
|
47
|
+
const envRegex = /^\s*\[env:([a-zA-Z_][a-zA-Z0-9_-]*)\]\s*$/;
|
|
48
|
+
const secretConnectionStringRegex = /^(\s*secret\s+connectionString\s+)([a-zA-Z_][a-zA-Z0-9_]*)(\s*=\s*)(.+)$/;
|
|
49
|
+
const secretRegex = /^(\s*secret\s+)([a-zA-Z_][a-zA-Z0-9_]*)(\s*=\s*)(.+)$/;
|
|
50
|
+
function splitContentLines(content) {
|
|
51
|
+
return content.split(/\r?\n/);
|
|
52
|
+
}
|
|
53
|
+
function detectEol(content) {
|
|
54
|
+
return content.includes('\r\n') ? '\r\n' : '\n';
|
|
55
|
+
}
|
|
56
|
+
function isNornenvFilePath(filePath) {
|
|
57
|
+
return path.basename(filePath).toLowerCase() === '.nornenv';
|
|
58
|
+
}
|
|
59
|
+
function isNornenvDocumentLike(languageId, filePath) {
|
|
60
|
+
return languageId === 'nornenv' || (!!filePath && isNornenvFilePath(filePath));
|
|
61
|
+
}
|
|
62
|
+
function extractSecretLines(content, filePath) {
|
|
63
|
+
const lines = splitContentLines(content);
|
|
64
|
+
const secrets = [];
|
|
65
|
+
let currentEnv;
|
|
66
|
+
for (let i = 0; i < lines.length; i++) {
|
|
67
|
+
const line = lines[i];
|
|
68
|
+
const trimmed = line.trim();
|
|
69
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const envMatch = trimmed.match(envRegex);
|
|
73
|
+
if (envMatch) {
|
|
74
|
+
currentEnv = envMatch[1];
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const secretConnectionStringMatch = line.match(secretConnectionStringRegex);
|
|
78
|
+
const secretMatch = line.match(secretRegex);
|
|
79
|
+
if (!secretConnectionStringMatch && !secretMatch) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const secretName = secretConnectionStringMatch
|
|
83
|
+
? `${secretConnectionStringMatch[2]}_connectionString`
|
|
84
|
+
: secretMatch[2];
|
|
85
|
+
const value = (secretConnectionStringMatch?.[4] ?? secretMatch[4]).trim();
|
|
86
|
+
const encrypted = (0, crypto_1.isEncryptedSecretValue)(value);
|
|
87
|
+
let kid;
|
|
88
|
+
if (encrypted) {
|
|
89
|
+
const parsed = (0, crypto_1.parseEncryptedSecretValue)(value);
|
|
90
|
+
if (parsed.ok) {
|
|
91
|
+
kid = parsed.parsed.kid;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
secrets.push({
|
|
95
|
+
filePath,
|
|
96
|
+
lineNumber: i,
|
|
97
|
+
envName: currentEnv,
|
|
98
|
+
name: secretName,
|
|
99
|
+
value,
|
|
100
|
+
encrypted,
|
|
101
|
+
kid
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return secrets;
|
|
105
|
+
}
|
|
106
|
+
function updateSecretLineValue(content, lineNumber, newValue) {
|
|
107
|
+
const lines = splitContentLines(content);
|
|
108
|
+
if (lineNumber < 0 || lineNumber >= lines.length) {
|
|
109
|
+
throw new Error(`Line ${lineNumber + 1} is out of range.`);
|
|
110
|
+
}
|
|
111
|
+
const line = lines[lineNumber];
|
|
112
|
+
const secretConnectionStringMatch = line.match(secretConnectionStringRegex);
|
|
113
|
+
const secretMatch = line.match(secretRegex);
|
|
114
|
+
if (!secretConnectionStringMatch && !secretMatch) {
|
|
115
|
+
throw new Error(`Line ${lineNumber + 1} is not a secret declaration.`);
|
|
116
|
+
}
|
|
117
|
+
if (secretConnectionStringMatch) {
|
|
118
|
+
lines[lineNumber] = `${secretConnectionStringMatch[1]}${secretConnectionStringMatch[2]}${secretConnectionStringMatch[3]}${newValue}`;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
lines[lineNumber] = `${secretMatch[1]}${secretMatch[2]}${secretMatch[3]}${newValue}`;
|
|
122
|
+
}
|
|
123
|
+
return lines.join(detectEol(content));
|
|
124
|
+
}
|
|
125
|
+
function findSecretLine(content, variableName, envName) {
|
|
126
|
+
const secretLines = extractSecretLines(content);
|
|
127
|
+
if (envName !== undefined) {
|
|
128
|
+
return secretLines.find(secret => secret.name === variableName && secret.envName === envName);
|
|
129
|
+
}
|
|
130
|
+
const commonMatch = secretLines.find(secret => secret.name === variableName && secret.envName === undefined);
|
|
131
|
+
if (commonMatch) {
|
|
132
|
+
return commonMatch;
|
|
133
|
+
}
|
|
134
|
+
return secretLines.find(secret => secret.name === variableName);
|
|
135
|
+
}
|
|
136
|
+
function loadSecretLine(filePath, variableName, envName) {
|
|
137
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
138
|
+
const secret = findSecretLine(content, variableName, envName);
|
|
139
|
+
if (!secret) {
|
|
140
|
+
const envLabel = envName ? ` in [env:${envName}]` : '';
|
|
141
|
+
throw new Error(`Secret '${variableName}' not found${envLabel} in ${filePath}`);
|
|
142
|
+
}
|
|
143
|
+
return { content, secret };
|
|
144
|
+
}
|
|
145
|
+
function writeSecretLine(filePath, content) {
|
|
146
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
147
|
+
}
|
|
148
|
+
function discoverNornenvFiles(targetPath) {
|
|
149
|
+
const resolved = path.resolve(targetPath);
|
|
150
|
+
if (!fs.existsSync(resolved)) {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
const stats = fs.statSync(resolved);
|
|
154
|
+
if (stats.isFile()) {
|
|
155
|
+
return isNornenvFilePath(resolved) ? [resolved] : [];
|
|
156
|
+
}
|
|
157
|
+
const results = [];
|
|
158
|
+
const walk = (dir) => {
|
|
159
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
const fullPath = path.join(dir, entry.name);
|
|
162
|
+
if (entry.isDirectory()) {
|
|
163
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist' || entry.name === 'out') {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
walk(fullPath);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (entry.isFile() && isNornenvFilePath(entry.name)) {
|
|
170
|
+
results.push(fullPath);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
walk(resolved);
|
|
175
|
+
return results.sort();
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=envFileSecrets.js.map
|