@keynv/cli 0.1.0-rc.18 → 0.1.0-rc.19
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/dist/index.js +724 -227
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import { createHash, randomBytes } from 'crypto';
|
|
4
|
-
import { readFileSync,
|
|
4
|
+
import { readFileSync, existsSync, writeFileSync, statSync, realpathSync, readdirSync, renameSync, mkdirSync, rmSync, lstatSync } from 'fs';
|
|
5
5
|
import { hostname, platform, homedir } from 'os';
|
|
6
6
|
import * as nodePath from 'path';
|
|
7
|
-
import { relative, join, isAbsolute, resolve, dirname, basename } from 'path';
|
|
7
|
+
import { relative, join, isAbsolute, resolve, sep, dirname, basename } from 'path';
|
|
8
8
|
import { Entry } from '@napi-rs/keyring';
|
|
9
9
|
import { styleText, stripVTControlCharacters } from 'util';
|
|
10
10
|
import j2, { stdin, stdout } from 'process';
|
|
@@ -1057,7 +1057,7 @@ var require_lib = __commonJS({
|
|
|
1057
1057
|
var VERSION, AGENT;
|
|
1058
1058
|
var init_version = __esm({
|
|
1059
1059
|
"src/version.ts"() {
|
|
1060
|
-
VERSION = "0.1.0-rc.
|
|
1060
|
+
VERSION = "0.1.0-rc.19" ;
|
|
1061
1061
|
AGENT = `keynv-cli/${VERSION}`;
|
|
1062
1062
|
}
|
|
1063
1063
|
});
|
|
@@ -5985,11 +5985,13 @@ var init_envFile = __esm({
|
|
|
5985
5985
|
function aiContextBody() {
|
|
5986
5986
|
return `## keynv (secrets)
|
|
5987
5987
|
|
|
5988
|
-
This project uses [keynv](https://keynv.dev) for secrets.
|
|
5988
|
+
This project uses [keynv](https://keynv.dev) for secrets. \`.keynv.env\` files contain **alias references** like \`OPENAI_API_KEY=@projectname.dev.openai-key\` \u2014 these are NOT real values, they are references to secrets stored in the keynv vault.
|
|
5989
|
+
|
|
5990
|
+
In a monorepo, expect **one \`.keynv.env\` per app directory** (e.g. \`apps/api/.keynv.env\`, \`apps/web/.keynv.env\`) next to the original \`.env\` file that was migrated. \`keynv exec\` walks upward from the current directory and loads the nearest one, so running it from inside an app's folder picks up that app's aliases.
|
|
5989
5991
|
|
|
5990
5992
|
### Mental model
|
|
5991
5993
|
|
|
5992
|
-
- **\`.keynv.env\`** \u2014 checked into git, contains aliases. Safe to read, edit, commit.
|
|
5994
|
+
- **\`.keynv.env\`** \u2014 checked into git, contains aliases. Safe to read, edit, commit. May appear in subdirectories of a monorepo.
|
|
5993
5995
|
- **Vault** \u2014 holds the real values. The \`keynv\` CLI reads it on demand.
|
|
5994
5996
|
- **\`keynv exec -- <command>\`** \u2014 forks the command in a subprocess where the real values are injected into env vars. The parent process (where you, the AI agent, run) NEVER sees the real values.
|
|
5995
5997
|
- **Redactor** \u2014 pipes subprocess stdout/stderr through a masker before they reach this terminal. Even if a script accidentally echoes a secret, you see \`<REDACTED:...>\` here.
|
|
@@ -6057,9 +6059,9 @@ ${block}
|
|
|
6057
6059
|
writeFileSync(path, next);
|
|
6058
6060
|
return "updated";
|
|
6059
6061
|
}
|
|
6060
|
-
const
|
|
6062
|
+
const sep2 = existing.length === 0 ? "" : existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
6061
6063
|
const trailingNewline = existing.endsWith("\n") ? "" : "\n";
|
|
6062
|
-
writeFileSync(path, `${existing}${trailingNewline}${
|
|
6064
|
+
writeFileSync(path, `${existing}${trailingNewline}${sep2}${block}
|
|
6063
6065
|
`);
|
|
6064
6066
|
return "appended";
|
|
6065
6067
|
}
|
|
@@ -6071,6 +6073,175 @@ var init_ai_context = __esm({
|
|
|
6071
6073
|
KEYNV_BLOCK_END = "<!-- keynv:end -->";
|
|
6072
6074
|
}
|
|
6073
6075
|
});
|
|
6076
|
+
function backupEnvFile(absolutePath, now = /* @__PURE__ */ new Date()) {
|
|
6077
|
+
const plain = `${absolutePath}.backup`;
|
|
6078
|
+
if (!existsSync(plain)) {
|
|
6079
|
+
renameSync(absolutePath, plain);
|
|
6080
|
+
return { renamedTo: plain, usedTimestamp: false };
|
|
6081
|
+
}
|
|
6082
|
+
const stamped = `${absolutePath}.backup-${timestampSlug(now)}`;
|
|
6083
|
+
renameSync(absolutePath, stamped);
|
|
6084
|
+
return { renamedTo: stamped, usedTimestamp: true };
|
|
6085
|
+
}
|
|
6086
|
+
function timestampSlug(d2 = /* @__PURE__ */ new Date()) {
|
|
6087
|
+
const y = d2.getFullYear();
|
|
6088
|
+
const mo = pad2(d2.getMonth() + 1);
|
|
6089
|
+
const da = pad2(d2.getDate());
|
|
6090
|
+
const h2 = pad2(d2.getHours());
|
|
6091
|
+
const mi = pad2(d2.getMinutes());
|
|
6092
|
+
return `${y}${mo}${da}-${h2}${mi}`;
|
|
6093
|
+
}
|
|
6094
|
+
function pad2(n) {
|
|
6095
|
+
return n < 10 ? `0${n}` : String(n);
|
|
6096
|
+
}
|
|
6097
|
+
var init_backup = __esm({
|
|
6098
|
+
"src/init/backup.ts"() {
|
|
6099
|
+
}
|
|
6100
|
+
});
|
|
6101
|
+
|
|
6102
|
+
// src/init/collision.ts
|
|
6103
|
+
function planVaultKeys(sources) {
|
|
6104
|
+
const byDir = /* @__PURE__ */ new Map();
|
|
6105
|
+
const shadowedAccum = /* @__PURE__ */ new Map();
|
|
6106
|
+
for (const s of sources) {
|
|
6107
|
+
const dirKey = `${s.envName}|${s.file.containingDir}`;
|
|
6108
|
+
let perKey = byDir.get(dirKey);
|
|
6109
|
+
if (!perKey) {
|
|
6110
|
+
perKey = /* @__PURE__ */ new Map();
|
|
6111
|
+
byDir.set(dirKey, perKey);
|
|
6112
|
+
}
|
|
6113
|
+
const prior = perKey.get(s.name);
|
|
6114
|
+
if (prior) {
|
|
6115
|
+
const shadowKey = `${s.envName}|${s.file.containingDir}|${s.name}`;
|
|
6116
|
+
let note = shadowedAccum.get(shadowKey);
|
|
6117
|
+
if (!note) {
|
|
6118
|
+
note = {
|
|
6119
|
+
envName: s.envName,
|
|
6120
|
+
localKey: s.name,
|
|
6121
|
+
containingDir: s.file.containingDir,
|
|
6122
|
+
laterFile: s.file.name,
|
|
6123
|
+
earlierFiles: [prior.source.name]
|
|
6124
|
+
};
|
|
6125
|
+
shadowedAccum.set(shadowKey, note);
|
|
6126
|
+
} else {
|
|
6127
|
+
note.earlierFiles.push(note.laterFile);
|
|
6128
|
+
note.laterFile = s.file.name;
|
|
6129
|
+
}
|
|
6130
|
+
}
|
|
6131
|
+
perKey.set(s.name, {
|
|
6132
|
+
envName: s.envName,
|
|
6133
|
+
containingDir: s.file.containingDir,
|
|
6134
|
+
localKey: s.name,
|
|
6135
|
+
value: s.value,
|
|
6136
|
+
isAlias: s.isAlias,
|
|
6137
|
+
source: s.file,
|
|
6138
|
+
line: s.line
|
|
6139
|
+
});
|
|
6140
|
+
}
|
|
6141
|
+
const intraResolved = [];
|
|
6142
|
+
for (const perKey of byDir.values()) intraResolved.push(...perKey.values());
|
|
6143
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6144
|
+
for (const e2 of intraResolved) {
|
|
6145
|
+
const k2 = `${e2.envName}|${e2.localKey}`;
|
|
6146
|
+
let g = groups.get(k2);
|
|
6147
|
+
if (!g) {
|
|
6148
|
+
g = [];
|
|
6149
|
+
groups.set(k2, g);
|
|
6150
|
+
}
|
|
6151
|
+
g.push(e2);
|
|
6152
|
+
}
|
|
6153
|
+
const resolved = [];
|
|
6154
|
+
const renamed = [];
|
|
6155
|
+
const merged = [];
|
|
6156
|
+
for (const group of groups.values()) {
|
|
6157
|
+
if (group.length === 1) {
|
|
6158
|
+
const e2 = group[0];
|
|
6159
|
+
resolved.push({
|
|
6160
|
+
envName: e2.envName,
|
|
6161
|
+
localKey: e2.localKey,
|
|
6162
|
+
value: e2.value,
|
|
6163
|
+
isAlias: e2.isAlias,
|
|
6164
|
+
vaultKey: toAliasKey(e2.localKey),
|
|
6165
|
+
source: e2.source,
|
|
6166
|
+
line: e2.line
|
|
6167
|
+
});
|
|
6168
|
+
continue;
|
|
6169
|
+
}
|
|
6170
|
+
const allSameValue = group.every((e2) => e2.value === group[0]?.value);
|
|
6171
|
+
if (allSameValue) {
|
|
6172
|
+
const vaultKey = toAliasKey(group[0].localKey);
|
|
6173
|
+
merged.push({
|
|
6174
|
+
envName: group[0].envName,
|
|
6175
|
+
key: group[0].localKey,
|
|
6176
|
+
sources: group.map((e2) => e2.source)
|
|
6177
|
+
});
|
|
6178
|
+
for (const e2 of group) {
|
|
6179
|
+
resolved.push({
|
|
6180
|
+
envName: e2.envName,
|
|
6181
|
+
localKey: e2.localKey,
|
|
6182
|
+
value: e2.value,
|
|
6183
|
+
isAlias: e2.isAlias,
|
|
6184
|
+
vaultKey,
|
|
6185
|
+
source: e2.source,
|
|
6186
|
+
line: e2.line
|
|
6187
|
+
});
|
|
6188
|
+
}
|
|
6189
|
+
continue;
|
|
6190
|
+
}
|
|
6191
|
+
const initialSlugs = group.map((e2) => initialSlug(e2.source));
|
|
6192
|
+
const initialUnique = new Set(initialSlugs).size === initialSlugs.length;
|
|
6193
|
+
const slugs = initialUnique ? initialSlugs : group.map((e2) => fullSlug(e2.source));
|
|
6194
|
+
for (let i = 0; i < group.length; i++) {
|
|
6195
|
+
const e2 = group[i];
|
|
6196
|
+
const slug = slugs[i];
|
|
6197
|
+
const vaultKey = toAliasKey(`${slug}-${e2.localKey}`);
|
|
6198
|
+
renamed.push({
|
|
6199
|
+
envName: e2.envName,
|
|
6200
|
+
localKey: e2.localKey,
|
|
6201
|
+
vaultKey,
|
|
6202
|
+
source: e2.source,
|
|
6203
|
+
otherSources: group.filter((_, j3) => j3 !== i).map((g) => g.source)
|
|
6204
|
+
});
|
|
6205
|
+
resolved.push({
|
|
6206
|
+
envName: e2.envName,
|
|
6207
|
+
localKey: e2.localKey,
|
|
6208
|
+
value: e2.value,
|
|
6209
|
+
isAlias: e2.isAlias,
|
|
6210
|
+
vaultKey,
|
|
6211
|
+
source: e2.source,
|
|
6212
|
+
line: e2.line
|
|
6213
|
+
});
|
|
6214
|
+
}
|
|
6215
|
+
}
|
|
6216
|
+
return {
|
|
6217
|
+
resolved,
|
|
6218
|
+
renamed,
|
|
6219
|
+
merged,
|
|
6220
|
+
shadowed: [...shadowedAccum.values()]
|
|
6221
|
+
};
|
|
6222
|
+
}
|
|
6223
|
+
function initialSlug(file) {
|
|
6224
|
+
if (file.relativeDir === "") return "root";
|
|
6225
|
+
const parts = file.relativeDir.split("/");
|
|
6226
|
+
return (parts[parts.length - 1] ?? "root").toLowerCase();
|
|
6227
|
+
}
|
|
6228
|
+
function fullSlug(file) {
|
|
6229
|
+
if (file.relativeDir === "") return "root";
|
|
6230
|
+
return file.relativeDir.toLowerCase().replace(/\//g, "-");
|
|
6231
|
+
}
|
|
6232
|
+
function toAliasKey(name) {
|
|
6233
|
+
if (!name) return name;
|
|
6234
|
+
if (KEY_RE3.test(name)) return name;
|
|
6235
|
+
const normalised = name.toLowerCase().replace(/_/g, "-");
|
|
6236
|
+
if (KEY_RE3.test(normalised)) return normalised;
|
|
6237
|
+
return normalised.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64) || "key";
|
|
6238
|
+
}
|
|
6239
|
+
var KEY_RE3;
|
|
6240
|
+
var init_collision = __esm({
|
|
6241
|
+
"src/init/collision.ts"() {
|
|
6242
|
+
KEY_RE3 = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
6243
|
+
}
|
|
6244
|
+
});
|
|
6074
6245
|
function findProjectRoot(startDir) {
|
|
6075
6246
|
const result = walkUp(startDir, (dir) => {
|
|
6076
6247
|
for (const marker of PROJECT_MARKERS) {
|
|
@@ -6113,31 +6284,86 @@ function buildRoot(dir, marker) {
|
|
|
6113
6284
|
packageJsonInvalid: invalid
|
|
6114
6285
|
};
|
|
6115
6286
|
}
|
|
6116
|
-
function
|
|
6117
|
-
|
|
6287
|
+
function findEnvFilesRecursive(rootDir, opts = {}) {
|
|
6288
|
+
const maxDepth = Math.max(1, opts.maxDepth ?? DEFAULT_MAX_DEPTH);
|
|
6289
|
+
const ignore = opts.ignore ?? IGNORE_DIRS;
|
|
6290
|
+
let rootReal;
|
|
6118
6291
|
try {
|
|
6119
|
-
|
|
6292
|
+
rootReal = realpathSync(rootDir);
|
|
6120
6293
|
} catch {
|
|
6121
6294
|
return [];
|
|
6122
6295
|
}
|
|
6123
6296
|
const hits = [];
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6297
|
+
const seen = /* @__PURE__ */ new Set([rootReal]);
|
|
6298
|
+
const queue = [{ dir: rootDir, depth: 0 }];
|
|
6299
|
+
while (queue.length > 0) {
|
|
6300
|
+
const current = queue.shift();
|
|
6301
|
+
if (!current) break;
|
|
6302
|
+
const { dir, depth } = current;
|
|
6303
|
+
let entries;
|
|
6129
6304
|
try {
|
|
6130
|
-
|
|
6305
|
+
const raw = readdirSync(dir, { withFileTypes: true });
|
|
6306
|
+
entries = raw.map((d2) => ({
|
|
6307
|
+
name: d2.name,
|
|
6308
|
+
isFile: d2.isFile(),
|
|
6309
|
+
isDir: d2.isDirectory(),
|
|
6310
|
+
isSymlink: d2.isSymbolicLink()
|
|
6311
|
+
}));
|
|
6131
6312
|
} catch {
|
|
6132
6313
|
continue;
|
|
6133
6314
|
}
|
|
6134
|
-
const
|
|
6135
|
-
|
|
6136
|
-
|
|
6315
|
+
for (const entry2 of entries) {
|
|
6316
|
+
const full = join(dir, entry2.name);
|
|
6317
|
+
let isFile = entry2.isFile;
|
|
6318
|
+
let isDir = entry2.isDir;
|
|
6319
|
+
if (entry2.isSymlink) {
|
|
6320
|
+
try {
|
|
6321
|
+
const st3 = statSync(full);
|
|
6322
|
+
isFile = st3.isFile();
|
|
6323
|
+
if (st3.isDirectory()) isDir = false;
|
|
6324
|
+
} catch {
|
|
6325
|
+
continue;
|
|
6326
|
+
}
|
|
6327
|
+
}
|
|
6328
|
+
if (isFile) {
|
|
6329
|
+
if (!ENV_GLOB.test(entry2.name)) continue;
|
|
6330
|
+
if (ENV_EXAMPLE.test(entry2.name)) continue;
|
|
6331
|
+
if (KEYNV_ENV_EXCLUDE.test(entry2.name)) continue;
|
|
6332
|
+
const suffixMatch = entry2.name.match(/^\.env\.(.+)$/);
|
|
6333
|
+
const suffix = suffixMatch ? suffixMatch[1] : null;
|
|
6334
|
+
const rel = relative(rootDir, dir);
|
|
6335
|
+
const relativeDir = rel === "" ? "" : rel.split(sep).join("/");
|
|
6336
|
+
hits.push({
|
|
6337
|
+
path: full,
|
|
6338
|
+
name: entry2.name,
|
|
6339
|
+
suffix,
|
|
6340
|
+
suggestedEnv: suggestedEnvForSuffix(suffix),
|
|
6341
|
+
relativeDir,
|
|
6342
|
+
containingDir: dir
|
|
6343
|
+
});
|
|
6344
|
+
continue;
|
|
6345
|
+
}
|
|
6346
|
+
if (isDir && depth + 1 < maxDepth) {
|
|
6347
|
+
if (ignore.has(entry2.name)) continue;
|
|
6348
|
+
let realDir;
|
|
6349
|
+
try {
|
|
6350
|
+
realDir = realpathSync(full);
|
|
6351
|
+
} catch {
|
|
6352
|
+
continue;
|
|
6353
|
+
}
|
|
6354
|
+
if (seen.has(realDir)) continue;
|
|
6355
|
+
seen.add(realDir);
|
|
6356
|
+
queue.push({ dir: full, depth: depth + 1 });
|
|
6357
|
+
}
|
|
6358
|
+
}
|
|
6137
6359
|
}
|
|
6138
6360
|
hits.sort((a, b2) => {
|
|
6139
|
-
|
|
6140
|
-
|
|
6361
|
+
const aRoot = a.relativeDir === "";
|
|
6362
|
+
const bRoot = b2.relativeDir === "";
|
|
6363
|
+
if (aRoot !== bRoot) return aRoot ? -1 : 1;
|
|
6364
|
+
if (a.relativeDir !== b2.relativeDir) return a.relativeDir.localeCompare(b2.relativeDir);
|
|
6365
|
+
if (a.suffix === null && b2.suffix !== null) return -1;
|
|
6366
|
+
if (a.suffix !== null && b2.suffix === null) return 1;
|
|
6141
6367
|
return a.name.localeCompare(b2.name);
|
|
6142
6368
|
});
|
|
6143
6369
|
return hits;
|
|
@@ -6166,7 +6392,7 @@ function suggestedEnvForSuffix(suffix) {
|
|
|
6166
6392
|
function hasExistingKeynvEnv(rootDir) {
|
|
6167
6393
|
return existsSync(join(rootDir, KEYNV_ENV_BASENAME));
|
|
6168
6394
|
}
|
|
6169
|
-
var PROJECT_MARKERS, GIT_MARKER, ENV_GLOB, ENV_EXAMPLE, KEYNV_ENV_BASENAME;
|
|
6395
|
+
var PROJECT_MARKERS, GIT_MARKER, ENV_GLOB, ENV_EXAMPLE, KEYNV_ENV_BASENAME, KEYNV_ENV_EXCLUDE, IGNORE_DIRS, DEFAULT_MAX_DEPTH;
|
|
6170
6396
|
var init_detect = __esm({
|
|
6171
6397
|
"src/init/detect.ts"() {
|
|
6172
6398
|
init_fs();
|
|
@@ -6184,6 +6410,74 @@ var init_detect = __esm({
|
|
|
6184
6410
|
ENV_GLOB = /^\.env(\.[A-Za-z0-9_-]+)?$/;
|
|
6185
6411
|
ENV_EXAMPLE = /^\.env\.(example|sample|template|dist|defaults)$/;
|
|
6186
6412
|
KEYNV_ENV_BASENAME = ".keynv.env";
|
|
6413
|
+
KEYNV_ENV_EXCLUDE = /^\.keynv\.(.+\.)?env$/;
|
|
6414
|
+
IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
6415
|
+
// VCS + git hooks
|
|
6416
|
+
".git",
|
|
6417
|
+
".husky",
|
|
6418
|
+
// Package managers / dep caches
|
|
6419
|
+
"node_modules",
|
|
6420
|
+
".yarn",
|
|
6421
|
+
// Yarn Berry / PnP cache
|
|
6422
|
+
"bower_components",
|
|
6423
|
+
// Generic build / output / coverage dirs
|
|
6424
|
+
"dist",
|
|
6425
|
+
"build",
|
|
6426
|
+
"out",
|
|
6427
|
+
"coverage",
|
|
6428
|
+
".cache",
|
|
6429
|
+
".nyc_output",
|
|
6430
|
+
// Rust / Go / PHP
|
|
6431
|
+
"target",
|
|
6432
|
+
"vendor",
|
|
6433
|
+
// JS / TS frameworks
|
|
6434
|
+
".next",
|
|
6435
|
+
// Next.js
|
|
6436
|
+
".turbo",
|
|
6437
|
+
// Turborepo
|
|
6438
|
+
".nuxt",
|
|
6439
|
+
// Nuxt 2/3
|
|
6440
|
+
".output",
|
|
6441
|
+
// Nitro / Nuxt 3 build output
|
|
6442
|
+
".svelte-kit",
|
|
6443
|
+
// SvelteKit
|
|
6444
|
+
".astro",
|
|
6445
|
+
// Astro
|
|
6446
|
+
".angular",
|
|
6447
|
+
// Angular CLI cache
|
|
6448
|
+
".parcel-cache",
|
|
6449
|
+
// Parcel bundler
|
|
6450
|
+
".docusaurus",
|
|
6451
|
+
// Docusaurus
|
|
6452
|
+
".expo",
|
|
6453
|
+
// Expo / React Native
|
|
6454
|
+
// Deployment / serverless platforms
|
|
6455
|
+
".vercel",
|
|
6456
|
+
".netlify",
|
|
6457
|
+
".wrangler",
|
|
6458
|
+
// Cloudflare Workers
|
|
6459
|
+
".serverless",
|
|
6460
|
+
// Serverless framework
|
|
6461
|
+
".sst",
|
|
6462
|
+
// SST
|
|
6463
|
+
".amplify",
|
|
6464
|
+
// AWS Amplify
|
|
6465
|
+
".firebase",
|
|
6466
|
+
// Firebase
|
|
6467
|
+
// Python tooling (mixed-language monorepos)
|
|
6468
|
+
".venv",
|
|
6469
|
+
"venv",
|
|
6470
|
+
"__pycache__",
|
|
6471
|
+
".tox",
|
|
6472
|
+
".mypy_cache",
|
|
6473
|
+
".pytest_cache",
|
|
6474
|
+
".ruff_cache",
|
|
6475
|
+
// IDE / editor state
|
|
6476
|
+
".idea",
|
|
6477
|
+
".vscode",
|
|
6478
|
+
".fleet"
|
|
6479
|
+
]);
|
|
6480
|
+
DEFAULT_MAX_DEPTH = 5;
|
|
6187
6481
|
}
|
|
6188
6482
|
});
|
|
6189
6483
|
|
|
@@ -8601,15 +8895,16 @@ var init_pickProject = __esm({
|
|
|
8601
8895
|
var init_exports = {};
|
|
8602
8896
|
__export(init_exports, {
|
|
8603
8897
|
UserCancelled: () => UserCancelled,
|
|
8898
|
+
composeKeynvEnv: () => composeKeynvEnv,
|
|
8604
8899
|
runInitFlow: () => runInitFlow
|
|
8605
8900
|
});
|
|
8606
|
-
function
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
const
|
|
8611
|
-
if (
|
|
8612
|
-
return
|
|
8901
|
+
function displayName(file) {
|
|
8902
|
+
return file.relativeDir === "" ? file.name : `${file.relativeDir}/${file.name}`;
|
|
8903
|
+
}
|
|
8904
|
+
function relFromRoot(rootPath, absPath) {
|
|
8905
|
+
const r = relative(rootPath, absPath);
|
|
8906
|
+
if (r === "") return ".";
|
|
8907
|
+
return r.split(/[\\/]/).filter(Boolean).join("/");
|
|
8613
8908
|
}
|
|
8614
8909
|
async function runInitFlow(client, opts) {
|
|
8615
8910
|
ge("Set up this project");
|
|
@@ -8623,11 +8918,11 @@ async function runInitFlow(client, opts) {
|
|
|
8623
8918
|
if (root.packageJsonInvalid) {
|
|
8624
8919
|
R2.warn(`package.json at ${root.path} is not valid JSON \u2014 script wrapping will be skipped.`);
|
|
8625
8920
|
}
|
|
8626
|
-
const envFiles =
|
|
8921
|
+
const envFiles = findEnvFilesRecursive(root.path);
|
|
8627
8922
|
const intoExisting = hasExistingKeynvEnv(root.path);
|
|
8628
8923
|
if (envFiles.length === 0 && !intoExisting) {
|
|
8629
8924
|
R2.info(
|
|
8630
|
-
`No .env files found in ${root.path}. There's nothing to migrate yet \u2014 create a .keynv.env by hand or run \`keynv exec\` once you have one.`
|
|
8925
|
+
`No .env files found in ${root.path} (scanned root and subdirectories). There's nothing to migrate yet \u2014 create a .keynv.env by hand or run \`keynv exec\` once you have one.`
|
|
8631
8926
|
);
|
|
8632
8927
|
ye("Nothing to do.");
|
|
8633
8928
|
return { exitCode: 0 };
|
|
@@ -8636,8 +8931,9 @@ async function runInitFlow(client, opts) {
|
|
|
8636
8931
|
[
|
|
8637
8932
|
`Project root: ${root.path}`,
|
|
8638
8933
|
`Marker: ${root.marker}`,
|
|
8639
|
-
envFiles.length > 0 ? `Found env files:
|
|
8640
|
-
|
|
8934
|
+
envFiles.length > 0 ? `Found env files:
|
|
8935
|
+
${envFiles.map((f) => ` ${displayName(f)}`).join("\n")}` : "Found env files: (none)",
|
|
8936
|
+
intoExisting ? "Existing root .keynv.env detected \u2014 will merge new entries in." : ""
|
|
8641
8937
|
].filter(Boolean).join("\n"),
|
|
8642
8938
|
"Detected"
|
|
8643
8939
|
);
|
|
@@ -8652,62 +8948,118 @@ async function runInitFlow(client, opts) {
|
|
|
8652
8948
|
return { exitCode: 130 };
|
|
8653
8949
|
}
|
|
8654
8950
|
const distinctEnvs = [...new Set(fileMapping.map((m) => m.envName))];
|
|
8655
|
-
const
|
|
8951
|
+
const allSources = [];
|
|
8656
8952
|
const skipped = [];
|
|
8657
|
-
for (const
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8953
|
+
for (const { file, envName } of fileMapping) {
|
|
8954
|
+
let parsed;
|
|
8955
|
+
try {
|
|
8956
|
+
parsed = parseEnvFile(readFileSync(file.path, "utf8"), file.path);
|
|
8957
|
+
} catch (err) {
|
|
8958
|
+
R2.warn(
|
|
8959
|
+
`${displayName(file)}: ${err instanceof Error ? err.message : String(err)} \u2014 skipping file`
|
|
8960
|
+
);
|
|
8961
|
+
continue;
|
|
8962
|
+
}
|
|
8963
|
+
for (const e2 of parsed) {
|
|
8661
8964
|
if (classifyEntry(e2.name, e2.value).verdict === "skip") {
|
|
8662
|
-
skipped.push({ env:
|
|
8663
|
-
|
|
8664
|
-
kept.push(e2);
|
|
8965
|
+
skipped.push({ env: envName, name: e2.name });
|
|
8966
|
+
continue;
|
|
8665
8967
|
}
|
|
8968
|
+
allSources.push({
|
|
8969
|
+
file,
|
|
8970
|
+
envName,
|
|
8971
|
+
name: e2.name,
|
|
8972
|
+
value: e2.value,
|
|
8973
|
+
isAlias: e2.isAlias,
|
|
8974
|
+
line: e2.line
|
|
8975
|
+
});
|
|
8666
8976
|
}
|
|
8667
|
-
perEnv.set(env2, kept);
|
|
8668
8977
|
}
|
|
8669
8978
|
if (skipped.length > 0) {
|
|
8670
|
-
const names = [...new Set(skipped.map((s) => s.
|
|
8979
|
+
const names = [...new Set(skipped.map((s) => s.name))];
|
|
8671
8980
|
R2.info(
|
|
8672
8981
|
`Skipped ${skipped.length} framework/shell-managed entr${skipped.length === 1 ? "y" : "ies"}: ${names.join(", ")}`
|
|
8673
8982
|
);
|
|
8674
8983
|
}
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8984
|
+
const plan = planVaultKeys(allSources);
|
|
8985
|
+
if (plan.shadowed.length > 0) {
|
|
8986
|
+
const detail = plan.shadowed.map(
|
|
8987
|
+
(s) => ` [${s.envName}] ${relFromRoot(root.path, s.containingDir)}/${s.localKey}: ${s.earlierFiles.join(", ")} -> ${s.laterFile}`
|
|
8988
|
+
).join("\n");
|
|
8989
|
+
R2.warn(
|
|
8990
|
+
`Some keys appear in multiple env files in the same directory mapped to the same env; using the last value (dotenv convention):
|
|
8991
|
+
${detail}`
|
|
8992
|
+
);
|
|
8993
|
+
}
|
|
8994
|
+
if (plan.merged.length > 0) {
|
|
8995
|
+
const detail = plan.merged.map(
|
|
8996
|
+
(m) => ` [${m.envName}] ${m.key}: same value across ${m.sources.map((s) => displayName(s)).join(", ")} -> one vault entry shared`
|
|
8997
|
+
).join("\n");
|
|
8998
|
+
R2.info(`Merged cross-app duplicates into a single vault entry:
|
|
8999
|
+
${detail}`);
|
|
9000
|
+
}
|
|
9001
|
+
if (plan.renamed.length > 0) {
|
|
9002
|
+
const byLocal = /* @__PURE__ */ new Map();
|
|
9003
|
+
for (const r of plan.renamed) {
|
|
9004
|
+
const k2 = `${r.envName}|${r.localKey}`;
|
|
9005
|
+
const arr = byLocal.get(k2) ?? [];
|
|
9006
|
+
arr.push(r);
|
|
9007
|
+
byLocal.set(k2, arr);
|
|
9008
|
+
}
|
|
9009
|
+
const detail = [...byLocal.values()].map((group) => {
|
|
9010
|
+
const head = group[0];
|
|
9011
|
+
if (!head) return "";
|
|
9012
|
+
const sources = group.map((r) => `${displayName(r.source)} -> @vault:${r.vaultKey}`).join("\n ");
|
|
9013
|
+
return ` [${head.envName}] ${head.localKey} (values differ across apps):
|
|
9014
|
+
${sources}`;
|
|
9015
|
+
}).filter(Boolean).join("\n");
|
|
8679
9016
|
R2.warn(
|
|
8680
|
-
`
|
|
9017
|
+
`Vault keys were renamed to avoid cross-app collisions (your code keeps using the original names locally):
|
|
8681
9018
|
${detail}`
|
|
8682
9019
|
);
|
|
8683
9020
|
}
|
|
8684
|
-
|
|
8685
|
-
if (totalEntries === 0) {
|
|
9021
|
+
if (plan.resolved.length === 0) {
|
|
8686
9022
|
R2.info(
|
|
8687
9023
|
"All env files were empty or only contained framework-managed vars. Nothing to upload."
|
|
8688
9024
|
);
|
|
8689
9025
|
ye("Done.");
|
|
8690
9026
|
return { exitCode: 0 };
|
|
8691
9027
|
}
|
|
8692
|
-
const
|
|
8693
|
-
for (const
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
const
|
|
8698
|
-
const
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
9028
|
+
const groups = /* @__PURE__ */ new Map();
|
|
9029
|
+
for (const r of plan.resolved) {
|
|
9030
|
+
const composite = `${r.envName}|${r.vaultKey}`;
|
|
9031
|
+
let g = groups.get(composite);
|
|
9032
|
+
if (!g) {
|
|
9033
|
+
const c2 = classifyEntry(r.localKey, r.value);
|
|
9034
|
+
const hint = c2.hint || (r.isAlias ? "looks like an alias literal" : "no signal");
|
|
9035
|
+
const preview2 = r.isAlias ? r.value : previewValue(r.value, 28);
|
|
9036
|
+
const envTag = distinctEnvs.length > 1 ? `[${r.envName}] ` : "";
|
|
9037
|
+
const renamedTag = r.localKey !== r.vaultKey ? ` -> vault:${r.vaultKey}` : "";
|
|
9038
|
+
g = {
|
|
9039
|
+
composite,
|
|
9040
|
+
envName: r.envName,
|
|
9041
|
+
vaultKey: r.vaultKey,
|
|
9042
|
+
localKey: r.localKey,
|
|
9043
|
+
value: r.value,
|
|
9044
|
+
isAlias: r.isAlias,
|
|
8705
9045
|
verdict: c2.verdict,
|
|
8706
|
-
label: `${envTag}${
|
|
8707
|
-
hint
|
|
8708
|
-
|
|
9046
|
+
label: `${envTag}${r.localKey}${renamedTag} ${preview2}`,
|
|
9047
|
+
hint,
|
|
9048
|
+
sources: []
|
|
9049
|
+
};
|
|
9050
|
+
groups.set(composite, g);
|
|
9051
|
+
}
|
|
9052
|
+
g.sources.push(r);
|
|
9053
|
+
}
|
|
9054
|
+
for (const g of groups.values()) {
|
|
9055
|
+
const dirs = [...new Set(g.sources.map((s) => s.source.relativeDir || "<root>"))];
|
|
9056
|
+
if (dirs.length > 1) g.label = `${g.label} (shared by ${dirs.join(", ")})`;
|
|
9057
|
+
else if (envFiles.some((f) => f.relativeDir !== "")) {
|
|
9058
|
+
const only = dirs[0];
|
|
9059
|
+
if (only && only !== "<root>") g.label = `${g.label} [${only}]`;
|
|
8709
9060
|
}
|
|
8710
9061
|
}
|
|
9062
|
+
const choices = [...groups.values()];
|
|
8711
9063
|
const initialSecretSelection = choices.filter((c2) => c2.verdict === "secret" && !c2.isAlias).map((c2) => c2.composite);
|
|
8712
9064
|
const selectedComposites = unwrap(
|
|
8713
9065
|
await ve({
|
|
@@ -8729,18 +9081,24 @@ ${detail}`
|
|
|
8729
9081
|
scriptWrapSelection = scriptPlan.recommended.map((a) => a.name);
|
|
8730
9082
|
}
|
|
8731
9083
|
const perEnvCounts = distinctEnvs.map((env2) => {
|
|
8732
|
-
const
|
|
8733
|
-
const sec =
|
|
8734
|
-
const lit =
|
|
8735
|
-
return ` ${env2}: ${sec} secrets, ${lit} literals${env2 === defaultEnv ? " (default \u2014 written to .keynv.env)" : ""}`;
|
|
9084
|
+
const envGroups = choices.filter((g) => g.envName === env2);
|
|
9085
|
+
const sec = envGroups.filter((g) => selected.has(g.composite)).length;
|
|
9086
|
+
const lit = envGroups.length - sec;
|
|
9087
|
+
return ` ${env2}: ${sec} secrets, ${lit} literals${env2 === defaultEnv ? " (default \u2014 written to each app's .keynv.env)" : ""}`;
|
|
8736
9088
|
}).join("\n");
|
|
9089
|
+
const writeDirs = [...new Set(plan.resolved.map((r) => r.source.containingDir))];
|
|
9090
|
+
const writeDirsLines = writeDirs.map((d2) => ` ${relFromRoot(root.path, d2)}`).join("\n");
|
|
9091
|
+
const renameLine = plan.renamed.length > 0 ? `Renamed vault keys: ${plan.renamed.length} (to avoid cross-app collisions)` : "";
|
|
8737
9092
|
const planSummary = [
|
|
8738
9093
|
`Project: ${projectChoice.name}${projectChoice.created ? " (will be created)" : ""}`,
|
|
8739
9094
|
`Environments: ${distinctEnvs.join(", ")}`,
|
|
8740
9095
|
"Per-env breakdown:",
|
|
8741
9096
|
perEnvCounts,
|
|
8742
9097
|
`Script wraps: ${scriptWrapSelection.length}`,
|
|
8743
|
-
|
|
9098
|
+
`.keynv.env files: will be written under`,
|
|
9099
|
+
writeDirsLines,
|
|
9100
|
+
"Original .env files: rename to .env.backup after upload",
|
|
9101
|
+
renameLine,
|
|
8744
9102
|
opts.dryRun ? "Dry-run: no changes will be made." : ""
|
|
8745
9103
|
].filter(Boolean).join("\n");
|
|
8746
9104
|
Se(planSummary, "About to apply");
|
|
@@ -8755,100 +9113,98 @@ ${detail}`
|
|
|
8755
9113
|
}
|
|
8756
9114
|
const projectId2 = await ensureProjectAndEnvs(client, projectChoice, distinctEnvs);
|
|
8757
9115
|
if (projectId2 === null) return { exitCode: 1 };
|
|
8758
|
-
const
|
|
9116
|
+
const aliasByGroup = /* @__PURE__ */ new Map();
|
|
8759
9117
|
const failed = [];
|
|
8760
|
-
const
|
|
8761
|
-
if (
|
|
9118
|
+
const groupsToUpload = choices.filter((g) => selected.has(g.composite));
|
|
9119
|
+
if (groupsToUpload.length > 0) {
|
|
8762
9120
|
const s = ft();
|
|
8763
|
-
s.start(
|
|
9121
|
+
s.start(
|
|
9122
|
+
`Uploading ${groupsToUpload.length} secret${groupsToUpload.length === 1 ? "" : "s"}`
|
|
9123
|
+
);
|
|
8764
9124
|
let i = 0;
|
|
8765
|
-
for (const
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
project: projectChoice.name,
|
|
8780
|
-
environment: env2,
|
|
8781
|
-
key: aliasKey
|
|
8782
|
-
});
|
|
8783
|
-
if (alias2 === null) {
|
|
8784
|
-
failed.push({
|
|
8785
|
-
env: env2,
|
|
8786
|
-
name: e2.name,
|
|
8787
|
-
reason: `produced an invalid alias for project=${projectChoice.name} env=${env2} key=${aliasKey}`
|
|
8788
|
-
});
|
|
8789
|
-
} else {
|
|
8790
|
-
envUploaded.set(e2.name, alias2.literal);
|
|
8791
|
-
}
|
|
8792
|
-
} catch (err) {
|
|
9125
|
+
for (const g of groupsToUpload) {
|
|
9126
|
+
i++;
|
|
9127
|
+
s.message(`Uploading (${i}/${groupsToUpload.length}) [${g.envName}] ${g.vaultKey}`);
|
|
9128
|
+
try {
|
|
9129
|
+
await client.request(`/v1/projects/${projectId2}/secrets`, {
|
|
9130
|
+
method: "POST",
|
|
9131
|
+
body: { env: g.envName, key: g.vaultKey, value: g.value }
|
|
9132
|
+
});
|
|
9133
|
+
const alias2 = reference_exports.buildAlias({
|
|
9134
|
+
project: projectChoice.name,
|
|
9135
|
+
environment: g.envName,
|
|
9136
|
+
key: g.vaultKey
|
|
9137
|
+
});
|
|
9138
|
+
if (alias2 === null) {
|
|
8793
9139
|
failed.push({
|
|
8794
|
-
env:
|
|
8795
|
-
name:
|
|
8796
|
-
reason:
|
|
9140
|
+
env: g.envName,
|
|
9141
|
+
name: g.localKey,
|
|
9142
|
+
reason: `produced an invalid alias for project=${projectChoice.name} env=${g.envName} key=${g.vaultKey}`
|
|
8797
9143
|
});
|
|
9144
|
+
} else {
|
|
9145
|
+
aliasByGroup.set(g.composite, alias2.literal);
|
|
8798
9146
|
}
|
|
9147
|
+
} catch (err) {
|
|
9148
|
+
failed.push({
|
|
9149
|
+
env: g.envName,
|
|
9150
|
+
name: g.localKey,
|
|
9151
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
9152
|
+
});
|
|
8799
9153
|
}
|
|
8800
9154
|
}
|
|
8801
9155
|
if (failed.length === 0) {
|
|
8802
|
-
s.stop(`Uploaded ${
|
|
9156
|
+
s.stop(`Uploaded ${groupsToUpload.length} secret${groupsToUpload.length === 1 ? "" : "s"}`);
|
|
8803
9157
|
} else {
|
|
8804
9158
|
s.error(
|
|
8805
|
-
`${
|
|
9159
|
+
`${groupsToUpload.length - failed.length}/${groupsToUpload.length} uploaded; ${failed.length} failed`
|
|
8806
9160
|
);
|
|
8807
9161
|
for (const f of failed) R2.warn(` [${f.env}] ${f.name}: ${f.reason}`);
|
|
8808
9162
|
}
|
|
8809
9163
|
}
|
|
8810
|
-
const
|
|
8811
|
-
const
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
);
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
9164
|
+
const bucketKey = (dir, env2) => `${dir}|${env2}`;
|
|
9165
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
9166
|
+
for (const r of plan.resolved) {
|
|
9167
|
+
const k2 = bucketKey(r.source.containingDir, r.envName);
|
|
9168
|
+
let b2 = buckets.get(k2);
|
|
9169
|
+
if (!b2) {
|
|
9170
|
+
b2 = {
|
|
9171
|
+
containingDir: r.source.containingDir,
|
|
9172
|
+
envName: r.envName,
|
|
9173
|
+
aliasLines: [],
|
|
9174
|
+
literalEntries: []
|
|
9175
|
+
};
|
|
9176
|
+
buckets.set(k2, b2);
|
|
9177
|
+
}
|
|
9178
|
+
const groupKey = `${r.envName}|${r.vaultKey}`;
|
|
9179
|
+
const alias2 = aliasByGroup.get(groupKey);
|
|
9180
|
+
if (selected.has(groupKey) && alias2 !== void 0) {
|
|
9181
|
+
b2.aliasLines.push([r.localKey, alias2]);
|
|
9182
|
+
} else {
|
|
9183
|
+
b2.literalEntries.push({ name: r.localKey, value: r.value });
|
|
9184
|
+
}
|
|
8829
9185
|
}
|
|
8830
|
-
const
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
const envUploaded = uploadedByEnv.get(env2) ?? /* @__PURE__ */ new Map();
|
|
8835
|
-
const envLiterals = (perEnv.get(env2) ?? []).filter((e2) => !selected.has(`${env2}|${e2.name}`));
|
|
8836
|
-
const envFilePath = join(root.path, `.keynv.${env2}.env`);
|
|
9186
|
+
for (const b2 of buckets.values()) {
|
|
9187
|
+
const targetName = b2.envName === defaultEnv ? ".keynv.env" : `.keynv.${b2.envName}.env`;
|
|
9188
|
+
const targetPath = join(b2.containingDir, targetName);
|
|
9189
|
+
const dirIntoExisting = existsSync(targetPath);
|
|
8837
9190
|
try {
|
|
8838
9191
|
const lines = composeKeynvEnv({
|
|
8839
|
-
uploadedAliases:
|
|
8840
|
-
literals:
|
|
8841
|
-
mergeWithExisting: null
|
|
9192
|
+
uploadedAliases: new Map(b2.aliasLines),
|
|
9193
|
+
literals: b2.literalEntries.map((e2) => ({ name: e2.name, value: e2.value })),
|
|
9194
|
+
mergeWithExisting: dirIntoExisting ? readFileSync(targetPath, "utf8") : null
|
|
8842
9195
|
});
|
|
8843
|
-
writeFileSync(
|
|
9196
|
+
writeFileSync(targetPath, `${lines.join("\n")}
|
|
8844
9197
|
`);
|
|
9198
|
+
const relTarget = relFromRoot(root.path, targetPath);
|
|
9199
|
+
const total = b2.aliasLines.length + b2.literalEntries.length;
|
|
8845
9200
|
R2.success(
|
|
8846
|
-
|
|
9201
|
+
`${dirIntoExisting ? "Updated" : "Wrote"} ${relTarget} (${total} entr${total === 1 ? "y" : "ies"} from "${b2.envName}")`
|
|
8847
9202
|
);
|
|
8848
9203
|
} catch (err) {
|
|
8849
|
-
R2.
|
|
8850
|
-
`
|
|
9204
|
+
R2.error(
|
|
9205
|
+
`Failed to write ${relFromRoot(root.path, targetPath)}: ${err instanceof Error ? err.message : String(err)}`
|
|
8851
9206
|
);
|
|
9207
|
+
return { exitCode: 1 };
|
|
8852
9208
|
}
|
|
8853
9209
|
}
|
|
8854
9210
|
try {
|
|
@@ -8876,19 +9232,28 @@ ${detail}`
|
|
|
8876
9232
|
}
|
|
8877
9233
|
}
|
|
8878
9234
|
for (const f of envFiles) {
|
|
9235
|
+
const relSrc = relFromRoot(root.path, f.path);
|
|
8879
9236
|
try {
|
|
8880
|
-
|
|
8881
|
-
|
|
9237
|
+
const { renamedTo } = backupEnvFile(f.path);
|
|
9238
|
+
const relTarget = relFromRoot(root.path, renamedTo);
|
|
9239
|
+
R2.success(`Renamed ${relSrc} -> ${relTarget}`);
|
|
8882
9240
|
} catch (err) {
|
|
8883
|
-
R2.warn(`Could not
|
|
9241
|
+
R2.warn(`Could not rename ${relSrc}: ${err instanceof Error ? err.message : String(err)}`);
|
|
8884
9242
|
}
|
|
8885
9243
|
}
|
|
8886
9244
|
const otherEnvs = distinctEnvs.filter((e2) => e2 !== defaultEnv);
|
|
8887
9245
|
if (otherEnvs.length > 0) {
|
|
8888
9246
|
const lines = otherEnvs.map((env2) => {
|
|
8889
|
-
const
|
|
8890
|
-
|
|
8891
|
-
|
|
9247
|
+
const dirsForEnv = [
|
|
9248
|
+
...new Set(
|
|
9249
|
+
[...buckets.values()].filter((b2) => b2.envName === env2).map((b2) => b2.containingDir)
|
|
9250
|
+
)
|
|
9251
|
+
];
|
|
9252
|
+
if (dirsForEnv.length === 0) {
|
|
9253
|
+
return ` ${env2}: 0 secrets in vault (no alias file written)`;
|
|
9254
|
+
}
|
|
9255
|
+
const fileList = dirsForEnv.map((d2) => `${relFromRoot(root.path, d2)}/.keynv.${env2}.env`).join(", ");
|
|
9256
|
+
return ` ${env2}: ${fileList} (use \`keynv exec --from <file> -- <cmd>\`)`;
|
|
8892
9257
|
});
|
|
8893
9258
|
Se(lines.join("\n"), "Secrets in other envs");
|
|
8894
9259
|
}
|
|
@@ -8953,7 +9318,7 @@ async function pickFileEnvMapping(files, project) {
|
|
|
8953
9318
|
opts.push({ value: "__custom", label: "+ Custom env name\u2026" });
|
|
8954
9319
|
let envName = unwrap(
|
|
8955
9320
|
await Ee({
|
|
8956
|
-
message: `Map ${f
|
|
9321
|
+
message: `Map ${displayName(f)} to which keynv env?`,
|
|
8957
9322
|
options: opts,
|
|
8958
9323
|
initialValue: suggested
|
|
8959
9324
|
})
|
|
@@ -8970,43 +9335,6 @@ async function pickFileEnvMapping(files, project) {
|
|
|
8970
9335
|
}
|
|
8971
9336
|
return assignments;
|
|
8972
9337
|
}
|
|
8973
|
-
function parseAndMergePerEnv(mapping) {
|
|
8974
|
-
const acc = /* @__PURE__ */ new Map();
|
|
8975
|
-
for (const { file, envName } of mapping) {
|
|
8976
|
-
let entries;
|
|
8977
|
-
try {
|
|
8978
|
-
entries = parseEnvFile(readFileSync(file.path, "utf8"), file.path);
|
|
8979
|
-
} catch (err) {
|
|
8980
|
-
R2.warn(`${file.name}: ${err instanceof Error ? err.message : String(err)} \u2014 skipping file`);
|
|
8981
|
-
continue;
|
|
8982
|
-
}
|
|
8983
|
-
let envMap = acc.get(envName);
|
|
8984
|
-
if (!envMap) {
|
|
8985
|
-
envMap = /* @__PURE__ */ new Map();
|
|
8986
|
-
acc.set(envName, envMap);
|
|
8987
|
-
}
|
|
8988
|
-
for (const e2 of entries) {
|
|
8989
|
-
const existing = envMap.get(e2.name);
|
|
8990
|
-
if (existing) {
|
|
8991
|
-
existing.shadowedBy.push(file.name);
|
|
8992
|
-
existing.value = e2.value;
|
|
8993
|
-
existing.isAlias = e2.isAlias;
|
|
8994
|
-
} else {
|
|
8995
|
-
envMap.set(e2.name, {
|
|
8996
|
-
name: e2.name,
|
|
8997
|
-
value: e2.value,
|
|
8998
|
-
isAlias: e2.isAlias,
|
|
8999
|
-
source: file.name,
|
|
9000
|
-
sourceLine: e2.line,
|
|
9001
|
-
shadowedBy: []
|
|
9002
|
-
});
|
|
9003
|
-
}
|
|
9004
|
-
}
|
|
9005
|
-
}
|
|
9006
|
-
const out = /* @__PURE__ */ new Map();
|
|
9007
|
-
for (const [env2, m] of acc) out.set(env2, [...m.values()]);
|
|
9008
|
-
return out;
|
|
9009
|
-
}
|
|
9010
9338
|
function pickDefaultEnv(envs) {
|
|
9011
9339
|
if (envs.length === 0) return "dev";
|
|
9012
9340
|
if (envs.includes("dev")) return "dev";
|
|
@@ -9117,6 +9445,8 @@ var init_init = __esm({
|
|
|
9117
9445
|
init_dist();
|
|
9118
9446
|
init_envFile();
|
|
9119
9447
|
init_ai_context();
|
|
9448
|
+
init_backup();
|
|
9449
|
+
init_collision();
|
|
9120
9450
|
init_detect();
|
|
9121
9451
|
init_heuristics();
|
|
9122
9452
|
init_script_wrap();
|
|
@@ -9605,7 +9935,7 @@ var require_lib2 = __commonJS({
|
|
|
9605
9935
|
return `'${escapedValue}${value.slice(chunkIndex)}'`;
|
|
9606
9936
|
return `'${escapedValue}'`;
|
|
9607
9937
|
};
|
|
9608
|
-
var
|
|
9938
|
+
var pad22 = (value) => value < 10 ? "0" + value : "" + value;
|
|
9609
9939
|
var pad3 = (value) => value < 10 ? "00" + value : value < 100 ? "0" + value : "" + value;
|
|
9610
9940
|
var pad4 = (value) => value < 10 ? "000" + value : value < 100 ? "00" + value : value < 1e3 ? "0" + value : "" + value;
|
|
9611
9941
|
var convertTimezone = (tz) => {
|
|
@@ -9648,7 +9978,7 @@ var require_lib2 = __commonJS({
|
|
|
9648
9978
|
second = adjustedDate.getUTCSeconds();
|
|
9649
9979
|
millisecond = adjustedDate.getUTCMilliseconds();
|
|
9650
9980
|
}
|
|
9651
|
-
return escapeString(pad4(year) + "-" +
|
|
9981
|
+
return escapeString(pad4(year) + "-" + pad22(month) + "-" + pad22(day) + " " + pad22(hour) + ":" + pad22(minute) + ":" + pad22(second) + "." + pad3(millisecond));
|
|
9652
9982
|
};
|
|
9653
9983
|
exports.dateToString = dateToString;
|
|
9654
9984
|
var escapeId = (value, forbidQualified) => {
|
|
@@ -46566,6 +46896,8 @@ init_dist();
|
|
|
46566
46896
|
init_http();
|
|
46567
46897
|
init_envFile();
|
|
46568
46898
|
init_ai_context();
|
|
46899
|
+
init_backup();
|
|
46900
|
+
init_collision();
|
|
46569
46901
|
init_detect();
|
|
46570
46902
|
init_heuristics();
|
|
46571
46903
|
init_init();
|
|
@@ -46719,15 +47051,31 @@ var ProjectDeleteCommand = class extends Command {
|
|
|
46719
47051
|
// src/commands/init.ts
|
|
46720
47052
|
function toAliasKey2(name) {
|
|
46721
47053
|
if (!name) return name;
|
|
46722
|
-
const
|
|
46723
|
-
if (
|
|
47054
|
+
const KEY_RE4 = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
47055
|
+
if (KEY_RE4.test(name)) return name;
|
|
46724
47056
|
const normalised = name.toLowerCase().replace(/_/g, "-");
|
|
46725
|
-
if (
|
|
47057
|
+
if (KEY_RE4.test(normalised)) return normalised;
|
|
46726
47058
|
return normalised.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64) || "key";
|
|
46727
47059
|
}
|
|
46728
47060
|
function escapeRegExp2(value) {
|
|
46729
47061
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
46730
47062
|
}
|
|
47063
|
+
function displayHit(rootPath, file) {
|
|
47064
|
+
return relFromRootPath(rootPath, file.path);
|
|
47065
|
+
}
|
|
47066
|
+
function relFromRootPath(rootPath, absPath) {
|
|
47067
|
+
const r = relative(rootPath, absPath);
|
|
47068
|
+
if (r === "") return ".";
|
|
47069
|
+
return r.split(/[\\/]/).filter(Boolean).join("/");
|
|
47070
|
+
}
|
|
47071
|
+
function envBodyFor2(name) {
|
|
47072
|
+
const isProd = name === "prod" || name === "production";
|
|
47073
|
+
return {
|
|
47074
|
+
name,
|
|
47075
|
+
tier: isProd ? "production" : "non-production",
|
|
47076
|
+
require_approval: isProd
|
|
47077
|
+
};
|
|
47078
|
+
}
|
|
46731
47079
|
function writeKeynvEnvMappings(path, projectName, envName, secrets) {
|
|
46732
47080
|
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
46733
47081
|
const lines = [];
|
|
@@ -46921,9 +47269,15 @@ any prompts.
|
|
|
46921
47269
|
return 0;
|
|
46922
47270
|
}
|
|
46923
47271
|
/**
|
|
46924
|
-
* Auto-scan mode (--yes without explicit --env-file).
|
|
46925
|
-
*
|
|
46926
|
-
*
|
|
47272
|
+
* Auto-scan mode (--yes without explicit --env-file). Recursively
|
|
47273
|
+
* finds .env files across the project (root, apps/*, packages/*, …),
|
|
47274
|
+
* classifies entries, creates the project + needed envs, uploads
|
|
47275
|
+
* secrets, writes a `.keynv.env` next to each source `.env`, and
|
|
47276
|
+
* renames the originals to `.env.backup` — all without prompts.
|
|
47277
|
+
*
|
|
47278
|
+
* Multi-source key collisions are resolved by auto-prefixing the
|
|
47279
|
+
* vault key with the source directory slug; the user's code keeps
|
|
47280
|
+
* using the original env-var name locally.
|
|
46927
47281
|
*/
|
|
46928
47282
|
async runAutoScan(client) {
|
|
46929
47283
|
const root = findProjectRoot(process.cwd());
|
|
@@ -46931,74 +47285,207 @@ any prompts.
|
|
|
46931
47285
|
this.context.stderr.write("keynv: no project root found (no package.json, .git, etc.).\n");
|
|
46932
47286
|
return 1;
|
|
46933
47287
|
}
|
|
46934
|
-
const envFiles =
|
|
47288
|
+
const envFiles = findEnvFilesRecursive(root.path);
|
|
46935
47289
|
if (envFiles.length === 0) {
|
|
46936
|
-
this.context.stdout.write(
|
|
47290
|
+
this.context.stdout.write(
|
|
47291
|
+
"keynv: no .env files found (scanned root and subdirectories). Nothing to migrate.\n"
|
|
47292
|
+
);
|
|
46937
47293
|
return 0;
|
|
46938
47294
|
}
|
|
46939
47295
|
const projectName = this.project ?? root.suggestedName;
|
|
46940
|
-
const
|
|
46941
|
-
const
|
|
47296
|
+
const cliEnv = this.env;
|
|
47297
|
+
const allSources = [];
|
|
47298
|
+
const isSecret = /* @__PURE__ */ new Set();
|
|
47299
|
+
let skippedCount = 0;
|
|
46942
47300
|
for (const f of envFiles) {
|
|
46943
47301
|
try {
|
|
46944
47302
|
const entries = parseEnvFile(readFileSync(f.path, "utf8"), f.path);
|
|
47303
|
+
const envName = cliEnv ?? suggestedEnvForSuffix(f.suffix);
|
|
46945
47304
|
for (const e2 of entries) {
|
|
46946
|
-
|
|
46947
|
-
|
|
47305
|
+
const verdict = classifyEntry(e2.name, e2.value).verdict;
|
|
47306
|
+
if (verdict === "skip") {
|
|
47307
|
+
skippedCount++;
|
|
47308
|
+
continue;
|
|
46948
47309
|
}
|
|
47310
|
+
if (verdict === "secret" && !e2.isAlias) {
|
|
47311
|
+
isSecret.add(`${f.path}|${e2.name}`);
|
|
47312
|
+
}
|
|
47313
|
+
allSources.push({
|
|
47314
|
+
file: f,
|
|
47315
|
+
envName,
|
|
47316
|
+
name: e2.name,
|
|
47317
|
+
value: e2.value,
|
|
47318
|
+
isAlias: e2.isAlias,
|
|
47319
|
+
line: e2.line
|
|
47320
|
+
});
|
|
46949
47321
|
}
|
|
46950
47322
|
} catch {
|
|
46951
|
-
this.context.stderr.write(
|
|
46952
|
-
`)
|
|
47323
|
+
this.context.stderr.write(
|
|
47324
|
+
`keynv: warning \u2014 could not parse ${displayHit(root.path, f)}, skipping.
|
|
47325
|
+
`
|
|
47326
|
+
);
|
|
46953
47327
|
}
|
|
46954
47328
|
}
|
|
46955
|
-
if (
|
|
46956
|
-
this.context.stdout.write("keynv: no
|
|
47329
|
+
if (allSources.length === 0) {
|
|
47330
|
+
this.context.stdout.write("keynv: no migrable entries in .env files. Nothing to do.\n");
|
|
46957
47331
|
return 0;
|
|
46958
47332
|
}
|
|
47333
|
+
const plan = planVaultKeys(allSources);
|
|
47334
|
+
for (const r of plan.renamed) {
|
|
47335
|
+
this.context.stderr.write(
|
|
47336
|
+
`keynv: renamed ${r.localKey} -> ${r.vaultKey} in env ${r.envName} (collision with ${r.otherSources.map((s) => displayHit(root.path, s)).join(", ")})
|
|
47337
|
+
`
|
|
47338
|
+
);
|
|
47339
|
+
}
|
|
47340
|
+
for (const m of plan.merged) {
|
|
47341
|
+
this.context.stderr.write(
|
|
47342
|
+
`keynv: merged ${m.key} in env ${m.envName} (same value in ${m.sources.map((s) => displayHit(root.path, s)).join(", ")})
|
|
47343
|
+
`
|
|
47344
|
+
);
|
|
47345
|
+
}
|
|
47346
|
+
for (const s of plan.shadowed) {
|
|
47347
|
+
this.context.stderr.write(
|
|
47348
|
+
`keynv: dotenv last-wins: ${s.localKey} in ${relFromRootPath(root.path, s.containingDir)} (${s.earlierFiles.join(", ")} -> ${s.laterFile})
|
|
47349
|
+
`
|
|
47350
|
+
);
|
|
47351
|
+
}
|
|
47352
|
+
const distinctEnvs = [...new Set(plan.resolved.map((r) => r.envName))];
|
|
47353
|
+
const secretCount = plan.resolved.filter(
|
|
47354
|
+
(r) => isSecret.has(`${r.source.path}|${r.localKey}`)
|
|
47355
|
+
).length;
|
|
47356
|
+
const literalCount = plan.resolved.length - secretCount;
|
|
46959
47357
|
this.context.stdout.write(
|
|
46960
|
-
`keynv: auto-scan found ${
|
|
47358
|
+
`keynv: auto-scan found ${secretCount} secret(s) and ${literalCount} literal(s) across ${envFiles.length} file(s)${skippedCount > 0 ? `; skipped ${skippedCount} framework entr${skippedCount === 1 ? "y" : "ies"}` : ""}.
|
|
46961
47359
|
`
|
|
46962
47360
|
);
|
|
46963
47361
|
if (this.dryRun) {
|
|
46964
|
-
for (const
|
|
46965
|
-
const
|
|
46966
|
-
|
|
46967
|
-
|
|
47362
|
+
for (const r of plan.resolved) {
|
|
47363
|
+
const tag = isSecret.has(`${r.source.path}|${r.localKey}`) ? "secret" : "literal";
|
|
47364
|
+
const target = isSecret.has(`${r.source.path}|${r.localKey}`) ? `@${projectName}.${r.envName}.${r.vaultKey}` : r.value;
|
|
47365
|
+
this.context.stdout.write(
|
|
47366
|
+
` ${displayHit(root.path, r.source)}: ${r.localKey}=${target} [${tag}]
|
|
47367
|
+
`
|
|
47368
|
+
);
|
|
46968
47369
|
}
|
|
46969
47370
|
return 0;
|
|
46970
47371
|
}
|
|
46971
47372
|
let projectId2;
|
|
46972
47373
|
try {
|
|
46973
47374
|
projectId2 = await resolveProjectId(client, projectName);
|
|
47375
|
+
try {
|
|
47376
|
+
const detail = await client.request(`/v1/projects/${projectId2}`);
|
|
47377
|
+
const existing = new Set(detail.environments.map((e2) => e2.name));
|
|
47378
|
+
const missing = distinctEnvs.filter((e2) => !existing.has(e2));
|
|
47379
|
+
for (const env2 of missing) {
|
|
47380
|
+
await client.request(`/v1/projects/${projectId2}/environments`, {
|
|
47381
|
+
method: "POST",
|
|
47382
|
+
body: envBodyFor2(env2)
|
|
47383
|
+
});
|
|
47384
|
+
this.context.stdout.write(`keynv: added env "${env2}" to project "${projectName}".
|
|
47385
|
+
`);
|
|
47386
|
+
}
|
|
47387
|
+
} catch (err) {
|
|
47388
|
+
this.context.stderr.write(
|
|
47389
|
+
`keynv: warning \u2014 could not reconcile envs for ${projectName}: ${err instanceof Error ? err.message : String(err)}
|
|
47390
|
+
`
|
|
47391
|
+
);
|
|
47392
|
+
}
|
|
46974
47393
|
} catch {
|
|
46975
47394
|
const created = await client.request("/v1/projects", {
|
|
46976
47395
|
method: "POST",
|
|
46977
47396
|
body: {
|
|
46978
47397
|
name: projectName,
|
|
46979
|
-
environments:
|
|
47398
|
+
environments: distinctEnvs.map((name) => envBodyFor2(name))
|
|
46980
47399
|
}
|
|
46981
47400
|
});
|
|
46982
47401
|
projectId2 = created.id;
|
|
46983
|
-
this.context.stdout.write(
|
|
46984
|
-
`)
|
|
47402
|
+
this.context.stdout.write(
|
|
47403
|
+
`keynv: created project "${projectName}" (${projectId2}) with env(s): ${distinctEnvs.join(", ")}
|
|
47404
|
+
`
|
|
47405
|
+
);
|
|
47406
|
+
}
|
|
47407
|
+
const aliasByGroup = /* @__PURE__ */ new Map();
|
|
47408
|
+
const secretGroupMap = /* @__PURE__ */ new Map();
|
|
47409
|
+
for (const r of plan.resolved) {
|
|
47410
|
+
if (!isSecret.has(`${r.source.path}|${r.localKey}`)) continue;
|
|
47411
|
+
const k2 = `${r.envName}|${r.vaultKey}`;
|
|
47412
|
+
if (!secretGroupMap.has(k2)) {
|
|
47413
|
+
secretGroupMap.set(k2, { envName: r.envName, vaultKey: r.vaultKey, value: r.value });
|
|
47414
|
+
}
|
|
47415
|
+
}
|
|
47416
|
+
let uploaded = 0;
|
|
47417
|
+
const failed = [];
|
|
47418
|
+
for (const [composite, g] of secretGroupMap) {
|
|
47419
|
+
const alias2 = reference_exports.buildAlias({
|
|
47420
|
+
project: projectName,
|
|
47421
|
+
environment: g.envName,
|
|
47422
|
+
key: g.vaultKey
|
|
47423
|
+
});
|
|
47424
|
+
if (!alias2) {
|
|
47425
|
+
failed.push({ name: g.vaultKey, reason: `invalid alias key: ${g.vaultKey}` });
|
|
47426
|
+
continue;
|
|
47427
|
+
}
|
|
47428
|
+
try {
|
|
47429
|
+
await client.request(`/v1/projects/${projectId2}/secrets`, {
|
|
47430
|
+
method: "POST",
|
|
47431
|
+
body: { env: g.envName, key: g.vaultKey, value: g.value }
|
|
47432
|
+
});
|
|
47433
|
+
aliasByGroup.set(composite, alias2.literal);
|
|
47434
|
+
uploaded++;
|
|
47435
|
+
} catch (err) {
|
|
47436
|
+
failed.push({
|
|
47437
|
+
name: g.vaultKey,
|
|
47438
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
47439
|
+
});
|
|
47440
|
+
}
|
|
46985
47441
|
}
|
|
46986
|
-
const secretsWithKeys = secrets.map((s) => ({ ...s, aliasKey: toAliasKey2(s.name) }));
|
|
46987
|
-
const result = await this.uploadSecrets(
|
|
46988
|
-
client,
|
|
46989
|
-
projectId2,
|
|
46990
|
-
projectName,
|
|
46991
|
-
envName,
|
|
46992
|
-
secretsWithKeys
|
|
46993
|
-
);
|
|
46994
|
-
if (result !== 0) return result;
|
|
46995
|
-
const keynvEnvPath = join(root.path, ".keynv.env");
|
|
46996
|
-
const written = writeKeynvEnvMappings(keynvEnvPath, projectName, envName, secretsWithKeys);
|
|
46997
47442
|
this.context.stdout.write(
|
|
46998
|
-
|
|
46999
|
-
` : `keynv: ${keynvEnvPath} already up to date
|
|
47443
|
+
`keynv: uploaded ${uploaded}/${secretGroupMap.size} secret(s) to ${projectName}.
|
|
47000
47444
|
`
|
|
47001
47445
|
);
|
|
47446
|
+
if (failed.length > 0) {
|
|
47447
|
+
for (const f of failed) this.context.stderr.write(` failed: ${f.name} \u2014 ${f.reason}
|
|
47448
|
+
`);
|
|
47449
|
+
return 1;
|
|
47450
|
+
}
|
|
47451
|
+
const defaultEnv = distinctEnvs.includes("dev") ? "dev" : distinctEnvs[0];
|
|
47452
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
47453
|
+
for (const r of plan.resolved) {
|
|
47454
|
+
const k2 = `${r.source.containingDir}|${r.envName}`;
|
|
47455
|
+
let b2 = buckets.get(k2);
|
|
47456
|
+
if (!b2) {
|
|
47457
|
+
b2 = {
|
|
47458
|
+
containingDir: r.source.containingDir,
|
|
47459
|
+
envName: r.envName,
|
|
47460
|
+
aliasLines: [],
|
|
47461
|
+
literalEntries: []
|
|
47462
|
+
};
|
|
47463
|
+
buckets.set(k2, b2);
|
|
47464
|
+
}
|
|
47465
|
+
const isSec = isSecret.has(`${r.source.path}|${r.localKey}`);
|
|
47466
|
+
const alias2 = aliasByGroup.get(`${r.envName}|${r.vaultKey}`);
|
|
47467
|
+
if (isSec && alias2 !== void 0) {
|
|
47468
|
+
b2.aliasLines.push([r.localKey, alias2]);
|
|
47469
|
+
} else {
|
|
47470
|
+
b2.literalEntries.push({ name: r.localKey, value: r.value });
|
|
47471
|
+
}
|
|
47472
|
+
}
|
|
47473
|
+
for (const b2 of buckets.values()) {
|
|
47474
|
+
const targetName = b2.envName === defaultEnv ? ".keynv.env" : `.keynv.${b2.envName}.env`;
|
|
47475
|
+
const targetPath = join(b2.containingDir, targetName);
|
|
47476
|
+
const dirIntoExisting = existsSync(targetPath);
|
|
47477
|
+
const lines = composeKeynvEnv({
|
|
47478
|
+
uploadedAliases: new Map(b2.aliasLines),
|
|
47479
|
+
literals: b2.literalEntries,
|
|
47480
|
+
mergeWithExisting: dirIntoExisting ? readFileSync(targetPath, "utf8") : null
|
|
47481
|
+
});
|
|
47482
|
+
writeFileSync(targetPath, `${lines.join("\n")}
|
|
47483
|
+
`);
|
|
47484
|
+
this.context.stdout.write(
|
|
47485
|
+
`keynv: ${dirIntoExisting ? "updated" : "wrote"} ${relFromRootPath(root.path, targetPath)}
|
|
47486
|
+
`
|
|
47487
|
+
);
|
|
47488
|
+
}
|
|
47002
47489
|
try {
|
|
47003
47490
|
const outcome = writeAiContext(root.path);
|
|
47004
47491
|
if (outcome === "created") this.context.stdout.write("keynv: wrote AGENTS.md\n");
|
|
@@ -47006,9 +47493,19 @@ any prompts.
|
|
|
47006
47493
|
this.context.stdout.write("keynv: warning \u2014 could not write AGENTS.md.\n");
|
|
47007
47494
|
}
|
|
47008
47495
|
for (const f of envFiles) {
|
|
47009
|
-
|
|
47010
|
-
|
|
47011
|
-
|
|
47496
|
+
const relSrc = displayHit(root.path, f);
|
|
47497
|
+
try {
|
|
47498
|
+
const { renamedTo } = backupEnvFile(f.path);
|
|
47499
|
+
this.context.stdout.write(
|
|
47500
|
+
`keynv: renamed ${relSrc} -> ${relFromRootPath(root.path, renamedTo)}
|
|
47501
|
+
`
|
|
47502
|
+
);
|
|
47503
|
+
} catch (err) {
|
|
47504
|
+
this.context.stderr.write(
|
|
47505
|
+
`keynv: warning \u2014 could not rename ${relSrc}: ${err instanceof Error ? err.message : String(err)}
|
|
47506
|
+
`
|
|
47507
|
+
);
|
|
47508
|
+
}
|
|
47012
47509
|
}
|
|
47013
47510
|
this.context.stdout.write(
|
|
47014
47511
|
"keynv: done. Use `keynv exec` to run commands with resolved secrets.\n"
|