@keynv/cli 0.1.0-rc.18 → 0.1.0-rc.20
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 +746 -228
- 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.20" ;
|
|
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");
|
|
8679
8989
|
R2.warn(
|
|
8680
|
-
`Some keys appear in multiple env files mapped to the same env; using the last value (dotenv convention):
|
|
8990
|
+
`Some keys appear in multiple env files in the same directory mapped to the same env; using the last value (dotenv convention):
|
|
8681
8991
|
${detail}`
|
|
8682
8992
|
);
|
|
8683
8993
|
}
|
|
8684
|
-
|
|
8685
|
-
|
|
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");
|
|
9016
|
+
R2.warn(
|
|
9017
|
+
`Vault keys were renamed to avoid cross-app collisions (your code keeps using the original names locally):
|
|
9018
|
+
${detail}`
|
|
9019
|
+
);
|
|
9020
|
+
}
|
|
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();
|
|
@@ -9207,7 +9537,7 @@ async function runBrowserAuth(serverUrl) {
|
|
|
9207
9537
|
);
|
|
9208
9538
|
}
|
|
9209
9539
|
const deadline = Date.now() + start.expires_in * 1e3;
|
|
9210
|
-
|
|
9540
|
+
let intervalMs = Math.max(1, start.interval) * 1e3;
|
|
9211
9541
|
while (Date.now() < deadline) {
|
|
9212
9542
|
await sleep(intervalMs);
|
|
9213
9543
|
const pollRes = await fetch(new URL("/v1/auth/cli/browser/poll", serverUrl).toString(), {
|
|
@@ -9216,6 +9546,14 @@ async function runBrowserAuth(serverUrl) {
|
|
|
9216
9546
|
body: JSON.stringify({ device_code: start.device_code })
|
|
9217
9547
|
});
|
|
9218
9548
|
if (pollRes.status === 202) continue;
|
|
9549
|
+
if (pollRes.status === 429) {
|
|
9550
|
+
await pollRes.text().catch(() => "");
|
|
9551
|
+
const retryAfterRaw = pollRes.headers.get("retry-after");
|
|
9552
|
+
const retryAfterMs = parseRetryAfterMs(retryAfterRaw);
|
|
9553
|
+
const nextInterval = Math.max(intervalMs * 2, retryAfterMs ?? 0, 5e3);
|
|
9554
|
+
intervalMs = Math.min(nextInterval, 6e4);
|
|
9555
|
+
continue;
|
|
9556
|
+
}
|
|
9219
9557
|
if (!pollRes.ok) {
|
|
9220
9558
|
throw new BrowserAuthError(
|
|
9221
9559
|
await errorMessage(pollRes, `Browser auth failed (${pollRes.status}).`)
|
|
@@ -9236,6 +9574,19 @@ async function runBrowserAuth(serverUrl) {
|
|
|
9236
9574
|
}
|
|
9237
9575
|
throw new BrowserAuthError("Browser auth timed out. Run `keynv` to try again.");
|
|
9238
9576
|
}
|
|
9577
|
+
function parseRetryAfterMs(header) {
|
|
9578
|
+
if (!header) return null;
|
|
9579
|
+
const trimmed = header.trim();
|
|
9580
|
+
if (trimmed === "") return null;
|
|
9581
|
+
if (/^\d+$/.test(trimmed)) {
|
|
9582
|
+
const seconds = Number.parseInt(trimmed, 10);
|
|
9583
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
9584
|
+
return null;
|
|
9585
|
+
}
|
|
9586
|
+
const parsed = Date.parse(trimmed);
|
|
9587
|
+
if (!Number.isFinite(parsed)) return null;
|
|
9588
|
+
return Math.max(0, parsed - Date.now());
|
|
9589
|
+
}
|
|
9239
9590
|
var BrowserAuthError;
|
|
9240
9591
|
var init_browser_auth = __esm({
|
|
9241
9592
|
"src/client/browser-auth.ts"() {
|
|
@@ -9605,7 +9956,7 @@ var require_lib2 = __commonJS({
|
|
|
9605
9956
|
return `'${escapedValue}${value.slice(chunkIndex)}'`;
|
|
9606
9957
|
return `'${escapedValue}'`;
|
|
9607
9958
|
};
|
|
9608
|
-
var
|
|
9959
|
+
var pad22 = (value) => value < 10 ? "0" + value : "" + value;
|
|
9609
9960
|
var pad3 = (value) => value < 10 ? "00" + value : value < 100 ? "0" + value : "" + value;
|
|
9610
9961
|
var pad4 = (value) => value < 10 ? "000" + value : value < 100 ? "00" + value : value < 1e3 ? "0" + value : "" + value;
|
|
9611
9962
|
var convertTimezone = (tz) => {
|
|
@@ -9648,7 +9999,7 @@ var require_lib2 = __commonJS({
|
|
|
9648
9999
|
second = adjustedDate.getUTCSeconds();
|
|
9649
10000
|
millisecond = adjustedDate.getUTCMilliseconds();
|
|
9650
10001
|
}
|
|
9651
|
-
return escapeString(pad4(year) + "-" +
|
|
10002
|
+
return escapeString(pad4(year) + "-" + pad22(month) + "-" + pad22(day) + " " + pad22(hour) + ":" + pad22(minute) + ":" + pad22(second) + "." + pad3(millisecond));
|
|
9652
10003
|
};
|
|
9653
10004
|
exports.dateToString = dateToString;
|
|
9654
10005
|
var escapeId = (value, forbidQualified) => {
|
|
@@ -46566,6 +46917,8 @@ init_dist();
|
|
|
46566
46917
|
init_http();
|
|
46567
46918
|
init_envFile();
|
|
46568
46919
|
init_ai_context();
|
|
46920
|
+
init_backup();
|
|
46921
|
+
init_collision();
|
|
46569
46922
|
init_detect();
|
|
46570
46923
|
init_heuristics();
|
|
46571
46924
|
init_init();
|
|
@@ -46719,15 +47072,31 @@ var ProjectDeleteCommand = class extends Command {
|
|
|
46719
47072
|
// src/commands/init.ts
|
|
46720
47073
|
function toAliasKey2(name) {
|
|
46721
47074
|
if (!name) return name;
|
|
46722
|
-
const
|
|
46723
|
-
if (
|
|
47075
|
+
const KEY_RE4 = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
47076
|
+
if (KEY_RE4.test(name)) return name;
|
|
46724
47077
|
const normalised = name.toLowerCase().replace(/_/g, "-");
|
|
46725
|
-
if (
|
|
47078
|
+
if (KEY_RE4.test(normalised)) return normalised;
|
|
46726
47079
|
return normalised.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64) || "key";
|
|
46727
47080
|
}
|
|
46728
47081
|
function escapeRegExp2(value) {
|
|
46729
47082
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
46730
47083
|
}
|
|
47084
|
+
function displayHit(rootPath, file) {
|
|
47085
|
+
return relFromRootPath(rootPath, file.path);
|
|
47086
|
+
}
|
|
47087
|
+
function relFromRootPath(rootPath, absPath) {
|
|
47088
|
+
const r = relative(rootPath, absPath);
|
|
47089
|
+
if (r === "") return ".";
|
|
47090
|
+
return r.split(/[\\/]/).filter(Boolean).join("/");
|
|
47091
|
+
}
|
|
47092
|
+
function envBodyFor2(name) {
|
|
47093
|
+
const isProd = name === "prod" || name === "production";
|
|
47094
|
+
return {
|
|
47095
|
+
name,
|
|
47096
|
+
tier: isProd ? "production" : "non-production",
|
|
47097
|
+
require_approval: isProd
|
|
47098
|
+
};
|
|
47099
|
+
}
|
|
46731
47100
|
function writeKeynvEnvMappings(path, projectName, envName, secrets) {
|
|
46732
47101
|
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
46733
47102
|
const lines = [];
|
|
@@ -46921,9 +47290,15 @@ any prompts.
|
|
|
46921
47290
|
return 0;
|
|
46922
47291
|
}
|
|
46923
47292
|
/**
|
|
46924
|
-
* Auto-scan mode (--yes without explicit --env-file).
|
|
46925
|
-
*
|
|
46926
|
-
*
|
|
47293
|
+
* Auto-scan mode (--yes without explicit --env-file). Recursively
|
|
47294
|
+
* finds .env files across the project (root, apps/*, packages/*, …),
|
|
47295
|
+
* classifies entries, creates the project + needed envs, uploads
|
|
47296
|
+
* secrets, writes a `.keynv.env` next to each source `.env`, and
|
|
47297
|
+
* renames the originals to `.env.backup` — all without prompts.
|
|
47298
|
+
*
|
|
47299
|
+
* Multi-source key collisions are resolved by auto-prefixing the
|
|
47300
|
+
* vault key with the source directory slug; the user's code keeps
|
|
47301
|
+
* using the original env-var name locally.
|
|
46927
47302
|
*/
|
|
46928
47303
|
async runAutoScan(client) {
|
|
46929
47304
|
const root = findProjectRoot(process.cwd());
|
|
@@ -46931,74 +47306,207 @@ any prompts.
|
|
|
46931
47306
|
this.context.stderr.write("keynv: no project root found (no package.json, .git, etc.).\n");
|
|
46932
47307
|
return 1;
|
|
46933
47308
|
}
|
|
46934
|
-
const envFiles =
|
|
47309
|
+
const envFiles = findEnvFilesRecursive(root.path);
|
|
46935
47310
|
if (envFiles.length === 0) {
|
|
46936
|
-
this.context.stdout.write(
|
|
47311
|
+
this.context.stdout.write(
|
|
47312
|
+
"keynv: no .env files found (scanned root and subdirectories). Nothing to migrate.\n"
|
|
47313
|
+
);
|
|
46937
47314
|
return 0;
|
|
46938
47315
|
}
|
|
46939
47316
|
const projectName = this.project ?? root.suggestedName;
|
|
46940
|
-
const
|
|
46941
|
-
const
|
|
47317
|
+
const cliEnv = this.env;
|
|
47318
|
+
const allSources = [];
|
|
47319
|
+
const isSecret = /* @__PURE__ */ new Set();
|
|
47320
|
+
let skippedCount = 0;
|
|
46942
47321
|
for (const f of envFiles) {
|
|
46943
47322
|
try {
|
|
46944
47323
|
const entries = parseEnvFile(readFileSync(f.path, "utf8"), f.path);
|
|
47324
|
+
const envName = cliEnv ?? suggestedEnvForSuffix(f.suffix);
|
|
46945
47325
|
for (const e2 of entries) {
|
|
46946
|
-
|
|
46947
|
-
|
|
47326
|
+
const verdict = classifyEntry(e2.name, e2.value).verdict;
|
|
47327
|
+
if (verdict === "skip") {
|
|
47328
|
+
skippedCount++;
|
|
47329
|
+
continue;
|
|
47330
|
+
}
|
|
47331
|
+
if (verdict === "secret" && !e2.isAlias) {
|
|
47332
|
+
isSecret.add(`${f.path}|${e2.name}`);
|
|
46948
47333
|
}
|
|
47334
|
+
allSources.push({
|
|
47335
|
+
file: f,
|
|
47336
|
+
envName,
|
|
47337
|
+
name: e2.name,
|
|
47338
|
+
value: e2.value,
|
|
47339
|
+
isAlias: e2.isAlias,
|
|
47340
|
+
line: e2.line
|
|
47341
|
+
});
|
|
46949
47342
|
}
|
|
46950
47343
|
} catch {
|
|
46951
|
-
this.context.stderr.write(
|
|
46952
|
-
`)
|
|
47344
|
+
this.context.stderr.write(
|
|
47345
|
+
`keynv: warning \u2014 could not parse ${displayHit(root.path, f)}, skipping.
|
|
47346
|
+
`
|
|
47347
|
+
);
|
|
46953
47348
|
}
|
|
46954
47349
|
}
|
|
46955
|
-
if (
|
|
46956
|
-
this.context.stdout.write("keynv: no
|
|
47350
|
+
if (allSources.length === 0) {
|
|
47351
|
+
this.context.stdout.write("keynv: no migrable entries in .env files. Nothing to do.\n");
|
|
46957
47352
|
return 0;
|
|
46958
47353
|
}
|
|
47354
|
+
const plan = planVaultKeys(allSources);
|
|
47355
|
+
for (const r of plan.renamed) {
|
|
47356
|
+
this.context.stderr.write(
|
|
47357
|
+
`keynv: renamed ${r.localKey} -> ${r.vaultKey} in env ${r.envName} (collision with ${r.otherSources.map((s) => displayHit(root.path, s)).join(", ")})
|
|
47358
|
+
`
|
|
47359
|
+
);
|
|
47360
|
+
}
|
|
47361
|
+
for (const m of plan.merged) {
|
|
47362
|
+
this.context.stderr.write(
|
|
47363
|
+
`keynv: merged ${m.key} in env ${m.envName} (same value in ${m.sources.map((s) => displayHit(root.path, s)).join(", ")})
|
|
47364
|
+
`
|
|
47365
|
+
);
|
|
47366
|
+
}
|
|
47367
|
+
for (const s of plan.shadowed) {
|
|
47368
|
+
this.context.stderr.write(
|
|
47369
|
+
`keynv: dotenv last-wins: ${s.localKey} in ${relFromRootPath(root.path, s.containingDir)} (${s.earlierFiles.join(", ")} -> ${s.laterFile})
|
|
47370
|
+
`
|
|
47371
|
+
);
|
|
47372
|
+
}
|
|
47373
|
+
const distinctEnvs = [...new Set(plan.resolved.map((r) => r.envName))];
|
|
47374
|
+
const secretCount = plan.resolved.filter(
|
|
47375
|
+
(r) => isSecret.has(`${r.source.path}|${r.localKey}`)
|
|
47376
|
+
).length;
|
|
47377
|
+
const literalCount = plan.resolved.length - secretCount;
|
|
46959
47378
|
this.context.stdout.write(
|
|
46960
|
-
`keynv: auto-scan found ${
|
|
47379
|
+
`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
47380
|
`
|
|
46962
47381
|
);
|
|
46963
47382
|
if (this.dryRun) {
|
|
46964
|
-
for (const
|
|
46965
|
-
const
|
|
46966
|
-
|
|
46967
|
-
|
|
47383
|
+
for (const r of plan.resolved) {
|
|
47384
|
+
const tag = isSecret.has(`${r.source.path}|${r.localKey}`) ? "secret" : "literal";
|
|
47385
|
+
const target = isSecret.has(`${r.source.path}|${r.localKey}`) ? `@${projectName}.${r.envName}.${r.vaultKey}` : r.value;
|
|
47386
|
+
this.context.stdout.write(
|
|
47387
|
+
` ${displayHit(root.path, r.source)}: ${r.localKey}=${target} [${tag}]
|
|
47388
|
+
`
|
|
47389
|
+
);
|
|
46968
47390
|
}
|
|
46969
47391
|
return 0;
|
|
46970
47392
|
}
|
|
46971
47393
|
let projectId2;
|
|
46972
47394
|
try {
|
|
46973
47395
|
projectId2 = await resolveProjectId(client, projectName);
|
|
47396
|
+
try {
|
|
47397
|
+
const detail = await client.request(`/v1/projects/${projectId2}`);
|
|
47398
|
+
const existing = new Set(detail.environments.map((e2) => e2.name));
|
|
47399
|
+
const missing = distinctEnvs.filter((e2) => !existing.has(e2));
|
|
47400
|
+
for (const env2 of missing) {
|
|
47401
|
+
await client.request(`/v1/projects/${projectId2}/environments`, {
|
|
47402
|
+
method: "POST",
|
|
47403
|
+
body: envBodyFor2(env2)
|
|
47404
|
+
});
|
|
47405
|
+
this.context.stdout.write(`keynv: added env "${env2}" to project "${projectName}".
|
|
47406
|
+
`);
|
|
47407
|
+
}
|
|
47408
|
+
} catch (err) {
|
|
47409
|
+
this.context.stderr.write(
|
|
47410
|
+
`keynv: warning \u2014 could not reconcile envs for ${projectName}: ${err instanceof Error ? err.message : String(err)}
|
|
47411
|
+
`
|
|
47412
|
+
);
|
|
47413
|
+
}
|
|
46974
47414
|
} catch {
|
|
46975
47415
|
const created = await client.request("/v1/projects", {
|
|
46976
47416
|
method: "POST",
|
|
46977
47417
|
body: {
|
|
46978
47418
|
name: projectName,
|
|
46979
|
-
environments:
|
|
47419
|
+
environments: distinctEnvs.map((name) => envBodyFor2(name))
|
|
46980
47420
|
}
|
|
46981
47421
|
});
|
|
46982
47422
|
projectId2 = created.id;
|
|
46983
|
-
this.context.stdout.write(
|
|
46984
|
-
`)
|
|
47423
|
+
this.context.stdout.write(
|
|
47424
|
+
`keynv: created project "${projectName}" (${projectId2}) with env(s): ${distinctEnvs.join(", ")}
|
|
47425
|
+
`
|
|
47426
|
+
);
|
|
47427
|
+
}
|
|
47428
|
+
const aliasByGroup = /* @__PURE__ */ new Map();
|
|
47429
|
+
const secretGroupMap = /* @__PURE__ */ new Map();
|
|
47430
|
+
for (const r of plan.resolved) {
|
|
47431
|
+
if (!isSecret.has(`${r.source.path}|${r.localKey}`)) continue;
|
|
47432
|
+
const k2 = `${r.envName}|${r.vaultKey}`;
|
|
47433
|
+
if (!secretGroupMap.has(k2)) {
|
|
47434
|
+
secretGroupMap.set(k2, { envName: r.envName, vaultKey: r.vaultKey, value: r.value });
|
|
47435
|
+
}
|
|
47436
|
+
}
|
|
47437
|
+
let uploaded = 0;
|
|
47438
|
+
const failed = [];
|
|
47439
|
+
for (const [composite, g] of secretGroupMap) {
|
|
47440
|
+
const alias2 = reference_exports.buildAlias({
|
|
47441
|
+
project: projectName,
|
|
47442
|
+
environment: g.envName,
|
|
47443
|
+
key: g.vaultKey
|
|
47444
|
+
});
|
|
47445
|
+
if (!alias2) {
|
|
47446
|
+
failed.push({ name: g.vaultKey, reason: `invalid alias key: ${g.vaultKey}` });
|
|
47447
|
+
continue;
|
|
47448
|
+
}
|
|
47449
|
+
try {
|
|
47450
|
+
await client.request(`/v1/projects/${projectId2}/secrets`, {
|
|
47451
|
+
method: "POST",
|
|
47452
|
+
body: { env: g.envName, key: g.vaultKey, value: g.value }
|
|
47453
|
+
});
|
|
47454
|
+
aliasByGroup.set(composite, alias2.literal);
|
|
47455
|
+
uploaded++;
|
|
47456
|
+
} catch (err) {
|
|
47457
|
+
failed.push({
|
|
47458
|
+
name: g.vaultKey,
|
|
47459
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
47460
|
+
});
|
|
47461
|
+
}
|
|
46985
47462
|
}
|
|
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
47463
|
this.context.stdout.write(
|
|
46998
|
-
|
|
46999
|
-
` : `keynv: ${keynvEnvPath} already up to date
|
|
47464
|
+
`keynv: uploaded ${uploaded}/${secretGroupMap.size} secret(s) to ${projectName}.
|
|
47000
47465
|
`
|
|
47001
47466
|
);
|
|
47467
|
+
if (failed.length > 0) {
|
|
47468
|
+
for (const f of failed) this.context.stderr.write(` failed: ${f.name} \u2014 ${f.reason}
|
|
47469
|
+
`);
|
|
47470
|
+
return 1;
|
|
47471
|
+
}
|
|
47472
|
+
const defaultEnv = distinctEnvs.includes("dev") ? "dev" : distinctEnvs[0];
|
|
47473
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
47474
|
+
for (const r of plan.resolved) {
|
|
47475
|
+
const k2 = `${r.source.containingDir}|${r.envName}`;
|
|
47476
|
+
let b2 = buckets.get(k2);
|
|
47477
|
+
if (!b2) {
|
|
47478
|
+
b2 = {
|
|
47479
|
+
containingDir: r.source.containingDir,
|
|
47480
|
+
envName: r.envName,
|
|
47481
|
+
aliasLines: [],
|
|
47482
|
+
literalEntries: []
|
|
47483
|
+
};
|
|
47484
|
+
buckets.set(k2, b2);
|
|
47485
|
+
}
|
|
47486
|
+
const isSec = isSecret.has(`${r.source.path}|${r.localKey}`);
|
|
47487
|
+
const alias2 = aliasByGroup.get(`${r.envName}|${r.vaultKey}`);
|
|
47488
|
+
if (isSec && alias2 !== void 0) {
|
|
47489
|
+
b2.aliasLines.push([r.localKey, alias2]);
|
|
47490
|
+
} else {
|
|
47491
|
+
b2.literalEntries.push({ name: r.localKey, value: r.value });
|
|
47492
|
+
}
|
|
47493
|
+
}
|
|
47494
|
+
for (const b2 of buckets.values()) {
|
|
47495
|
+
const targetName = b2.envName === defaultEnv ? ".keynv.env" : `.keynv.${b2.envName}.env`;
|
|
47496
|
+
const targetPath = join(b2.containingDir, targetName);
|
|
47497
|
+
const dirIntoExisting = existsSync(targetPath);
|
|
47498
|
+
const lines = composeKeynvEnv({
|
|
47499
|
+
uploadedAliases: new Map(b2.aliasLines),
|
|
47500
|
+
literals: b2.literalEntries,
|
|
47501
|
+
mergeWithExisting: dirIntoExisting ? readFileSync(targetPath, "utf8") : null
|
|
47502
|
+
});
|
|
47503
|
+
writeFileSync(targetPath, `${lines.join("\n")}
|
|
47504
|
+
`);
|
|
47505
|
+
this.context.stdout.write(
|
|
47506
|
+
`keynv: ${dirIntoExisting ? "updated" : "wrote"} ${relFromRootPath(root.path, targetPath)}
|
|
47507
|
+
`
|
|
47508
|
+
);
|
|
47509
|
+
}
|
|
47002
47510
|
try {
|
|
47003
47511
|
const outcome = writeAiContext(root.path);
|
|
47004
47512
|
if (outcome === "created") this.context.stdout.write("keynv: wrote AGENTS.md\n");
|
|
@@ -47006,9 +47514,19 @@ any prompts.
|
|
|
47006
47514
|
this.context.stdout.write("keynv: warning \u2014 could not write AGENTS.md.\n");
|
|
47007
47515
|
}
|
|
47008
47516
|
for (const f of envFiles) {
|
|
47009
|
-
|
|
47010
|
-
|
|
47011
|
-
|
|
47517
|
+
const relSrc = displayHit(root.path, f);
|
|
47518
|
+
try {
|
|
47519
|
+
const { renamedTo } = backupEnvFile(f.path);
|
|
47520
|
+
this.context.stdout.write(
|
|
47521
|
+
`keynv: renamed ${relSrc} -> ${relFromRootPath(root.path, renamedTo)}
|
|
47522
|
+
`
|
|
47523
|
+
);
|
|
47524
|
+
} catch (err) {
|
|
47525
|
+
this.context.stderr.write(
|
|
47526
|
+
`keynv: warning \u2014 could not rename ${relSrc}: ${err instanceof Error ? err.message : String(err)}
|
|
47527
|
+
`
|
|
47528
|
+
);
|
|
47529
|
+
}
|
|
47012
47530
|
}
|
|
47013
47531
|
this.context.stdout.write(
|
|
47014
47532
|
"keynv: done. Use `keynv exec` to run commands with resolved secrets.\n"
|