@keynv/cli 0.1.0-rc.17 → 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 +785 -246
- package/dist/index.js.map +1 -1
- package/package.json +3 -15
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
|
});
|
|
@@ -5778,6 +5778,13 @@ var init_http = __esm({
|
|
|
5778
5778
|
Check the server is running: curl ${serverUrl}/v1/health`
|
|
5779
5779
|
);
|
|
5780
5780
|
}
|
|
5781
|
+
} else {
|
|
5782
|
+
this.creds = null;
|
|
5783
|
+
throw clientError(
|
|
5784
|
+
401,
|
|
5785
|
+
"auth.session_expired",
|
|
5786
|
+
"Session expired. Run `keynv` to reconnect."
|
|
5787
|
+
);
|
|
5781
5788
|
}
|
|
5782
5789
|
}
|
|
5783
5790
|
if (res.status === 204) return void 0;
|
|
@@ -5978,11 +5985,13 @@ var init_envFile = __esm({
|
|
|
5978
5985
|
function aiContextBody() {
|
|
5979
5986
|
return `## keynv (secrets)
|
|
5980
5987
|
|
|
5981
|
-
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.
|
|
5982
5991
|
|
|
5983
5992
|
### Mental model
|
|
5984
5993
|
|
|
5985
|
-
- **\`.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.
|
|
5986
5995
|
- **Vault** \u2014 holds the real values. The \`keynv\` CLI reads it on demand.
|
|
5987
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.
|
|
5988
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.
|
|
@@ -6050,9 +6059,9 @@ ${block}
|
|
|
6050
6059
|
writeFileSync(path, next);
|
|
6051
6060
|
return "updated";
|
|
6052
6061
|
}
|
|
6053
|
-
const
|
|
6062
|
+
const sep2 = existing.length === 0 ? "" : existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
6054
6063
|
const trailingNewline = existing.endsWith("\n") ? "" : "\n";
|
|
6055
|
-
writeFileSync(path, `${existing}${trailingNewline}${
|
|
6064
|
+
writeFileSync(path, `${existing}${trailingNewline}${sep2}${block}
|
|
6056
6065
|
`);
|
|
6057
6066
|
return "appended";
|
|
6058
6067
|
}
|
|
@@ -6064,6 +6073,175 @@ var init_ai_context = __esm({
|
|
|
6064
6073
|
KEYNV_BLOCK_END = "<!-- keynv:end -->";
|
|
6065
6074
|
}
|
|
6066
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
|
+
});
|
|
6067
6245
|
function findProjectRoot(startDir) {
|
|
6068
6246
|
const result = walkUp(startDir, (dir) => {
|
|
6069
6247
|
for (const marker of PROJECT_MARKERS) {
|
|
@@ -6106,31 +6284,86 @@ function buildRoot(dir, marker) {
|
|
|
6106
6284
|
packageJsonInvalid: invalid
|
|
6107
6285
|
};
|
|
6108
6286
|
}
|
|
6109
|
-
function
|
|
6110
|
-
|
|
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;
|
|
6111
6291
|
try {
|
|
6112
|
-
|
|
6292
|
+
rootReal = realpathSync(rootDir);
|
|
6113
6293
|
} catch {
|
|
6114
6294
|
return [];
|
|
6115
6295
|
}
|
|
6116
6296
|
const hits = [];
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
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;
|
|
6122
6304
|
try {
|
|
6123
|
-
|
|
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
|
+
}));
|
|
6124
6312
|
} catch {
|
|
6125
6313
|
continue;
|
|
6126
6314
|
}
|
|
6127
|
-
const
|
|
6128
|
-
|
|
6129
|
-
|
|
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
|
+
}
|
|
6130
6359
|
}
|
|
6131
6360
|
hits.sort((a, b2) => {
|
|
6132
|
-
|
|
6133
|
-
|
|
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;
|
|
6134
6367
|
return a.name.localeCompare(b2.name);
|
|
6135
6368
|
});
|
|
6136
6369
|
return hits;
|
|
@@ -6159,7 +6392,7 @@ function suggestedEnvForSuffix(suffix) {
|
|
|
6159
6392
|
function hasExistingKeynvEnv(rootDir) {
|
|
6160
6393
|
return existsSync(join(rootDir, KEYNV_ENV_BASENAME));
|
|
6161
6394
|
}
|
|
6162
|
-
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;
|
|
6163
6396
|
var init_detect = __esm({
|
|
6164
6397
|
"src/init/detect.ts"() {
|
|
6165
6398
|
init_fs();
|
|
@@ -6177,6 +6410,74 @@ var init_detect = __esm({
|
|
|
6177
6410
|
ENV_GLOB = /^\.env(\.[A-Za-z0-9_-]+)?$/;
|
|
6178
6411
|
ENV_EXAMPLE = /^\.env\.(example|sample|template|dist|defaults)$/;
|
|
6179
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;
|
|
6180
6481
|
}
|
|
6181
6482
|
});
|
|
6182
6483
|
|
|
@@ -8594,15 +8895,16 @@ var init_pickProject = __esm({
|
|
|
8594
8895
|
var init_exports = {};
|
|
8595
8896
|
__export(init_exports, {
|
|
8596
8897
|
UserCancelled: () => UserCancelled,
|
|
8898
|
+
composeKeynvEnv: () => composeKeynvEnv,
|
|
8597
8899
|
runInitFlow: () => runInitFlow
|
|
8598
8900
|
});
|
|
8599
|
-
function
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
const
|
|
8604
|
-
if (
|
|
8605
|
-
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("/");
|
|
8606
8908
|
}
|
|
8607
8909
|
async function runInitFlow(client, opts) {
|
|
8608
8910
|
ge("Set up this project");
|
|
@@ -8616,11 +8918,11 @@ async function runInitFlow(client, opts) {
|
|
|
8616
8918
|
if (root.packageJsonInvalid) {
|
|
8617
8919
|
R2.warn(`package.json at ${root.path} is not valid JSON \u2014 script wrapping will be skipped.`);
|
|
8618
8920
|
}
|
|
8619
|
-
const envFiles =
|
|
8921
|
+
const envFiles = findEnvFilesRecursive(root.path);
|
|
8620
8922
|
const intoExisting = hasExistingKeynvEnv(root.path);
|
|
8621
8923
|
if (envFiles.length === 0 && !intoExisting) {
|
|
8622
8924
|
R2.info(
|
|
8623
|
-
`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.`
|
|
8624
8926
|
);
|
|
8625
8927
|
ye("Nothing to do.");
|
|
8626
8928
|
return { exitCode: 0 };
|
|
@@ -8629,8 +8931,9 @@ async function runInitFlow(client, opts) {
|
|
|
8629
8931
|
[
|
|
8630
8932
|
`Project root: ${root.path}`,
|
|
8631
8933
|
`Marker: ${root.marker}`,
|
|
8632
|
-
envFiles.length > 0 ? `Found env files:
|
|
8633
|
-
|
|
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." : ""
|
|
8634
8937
|
].filter(Boolean).join("\n"),
|
|
8635
8938
|
"Detected"
|
|
8636
8939
|
);
|
|
@@ -8645,62 +8948,118 @@ async function runInitFlow(client, opts) {
|
|
|
8645
8948
|
return { exitCode: 130 };
|
|
8646
8949
|
}
|
|
8647
8950
|
const distinctEnvs = [...new Set(fileMapping.map((m) => m.envName))];
|
|
8648
|
-
const
|
|
8951
|
+
const allSources = [];
|
|
8649
8952
|
const skipped = [];
|
|
8650
|
-
for (const
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
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) {
|
|
8654
8964
|
if (classifyEntry(e2.name, e2.value).verdict === "skip") {
|
|
8655
|
-
skipped.push({ env:
|
|
8656
|
-
|
|
8657
|
-
kept.push(e2);
|
|
8965
|
+
skipped.push({ env: envName, name: e2.name });
|
|
8966
|
+
continue;
|
|
8658
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
|
+
});
|
|
8659
8976
|
}
|
|
8660
|
-
perEnv.set(env2, kept);
|
|
8661
8977
|
}
|
|
8662
8978
|
if (skipped.length > 0) {
|
|
8663
|
-
const names = [...new Set(skipped.map((s) => s.
|
|
8979
|
+
const names = [...new Set(skipped.map((s) => s.name))];
|
|
8664
8980
|
R2.info(
|
|
8665
8981
|
`Skipped ${skipped.length} framework/shell-managed entr${skipped.length === 1 ? "y" : "ies"}: ${names.join(", ")}`
|
|
8666
8982
|
);
|
|
8667
8983
|
}
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
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");
|
|
8672
9016
|
R2.warn(
|
|
8673
|
-
`
|
|
9017
|
+
`Vault keys were renamed to avoid cross-app collisions (your code keeps using the original names locally):
|
|
8674
9018
|
${detail}`
|
|
8675
9019
|
);
|
|
8676
9020
|
}
|
|
8677
|
-
|
|
8678
|
-
if (totalEntries === 0) {
|
|
9021
|
+
if (plan.resolved.length === 0) {
|
|
8679
9022
|
R2.info(
|
|
8680
9023
|
"All env files were empty or only contained framework-managed vars. Nothing to upload."
|
|
8681
9024
|
);
|
|
8682
9025
|
ye("Done.");
|
|
8683
9026
|
return { exitCode: 0 };
|
|
8684
9027
|
}
|
|
8685
|
-
const
|
|
8686
|
-
for (const
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
const
|
|
8691
|
-
const
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
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,
|
|
8698
9045
|
verdict: c2.verdict,
|
|
8699
|
-
label: `${envTag}${
|
|
8700
|
-
hint
|
|
8701
|
-
|
|
9046
|
+
label: `${envTag}${r.localKey}${renamedTag} ${preview2}`,
|
|
9047
|
+
hint,
|
|
9048
|
+
sources: []
|
|
9049
|
+
};
|
|
9050
|
+
groups.set(composite, g);
|
|
8702
9051
|
}
|
|
9052
|
+
g.sources.push(r);
|
|
8703
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}]`;
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
const choices = [...groups.values()];
|
|
8704
9063
|
const initialSecretSelection = choices.filter((c2) => c2.verdict === "secret" && !c2.isAlias).map((c2) => c2.composite);
|
|
8705
9064
|
const selectedComposites = unwrap(
|
|
8706
9065
|
await ve({
|
|
@@ -8722,18 +9081,24 @@ ${detail}`
|
|
|
8722
9081
|
scriptWrapSelection = scriptPlan.recommended.map((a) => a.name);
|
|
8723
9082
|
}
|
|
8724
9083
|
const perEnvCounts = distinctEnvs.map((env2) => {
|
|
8725
|
-
const
|
|
8726
|
-
const sec =
|
|
8727
|
-
const lit =
|
|
8728
|
-
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)" : ""}`;
|
|
8729
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)` : "";
|
|
8730
9092
|
const planSummary = [
|
|
8731
9093
|
`Project: ${projectChoice.name}${projectChoice.created ? " (will be created)" : ""}`,
|
|
8732
9094
|
`Environments: ${distinctEnvs.join(", ")}`,
|
|
8733
9095
|
"Per-env breakdown:",
|
|
8734
9096
|
perEnvCounts,
|
|
8735
9097
|
`Script wraps: ${scriptWrapSelection.length}`,
|
|
8736
|
-
|
|
9098
|
+
`.keynv.env files: will be written under`,
|
|
9099
|
+
writeDirsLines,
|
|
9100
|
+
"Original .env files: rename to .env.backup after upload",
|
|
9101
|
+
renameLine,
|
|
8737
9102
|
opts.dryRun ? "Dry-run: no changes will be made." : ""
|
|
8738
9103
|
].filter(Boolean).join("\n");
|
|
8739
9104
|
Se(planSummary, "About to apply");
|
|
@@ -8748,100 +9113,98 @@ ${detail}`
|
|
|
8748
9113
|
}
|
|
8749
9114
|
const projectId2 = await ensureProjectAndEnvs(client, projectChoice, distinctEnvs);
|
|
8750
9115
|
if (projectId2 === null) return { exitCode: 1 };
|
|
8751
|
-
const
|
|
9116
|
+
const aliasByGroup = /* @__PURE__ */ new Map();
|
|
8752
9117
|
const failed = [];
|
|
8753
|
-
const
|
|
8754
|
-
if (
|
|
9118
|
+
const groupsToUpload = choices.filter((g) => selected.has(g.composite));
|
|
9119
|
+
if (groupsToUpload.length > 0) {
|
|
8755
9120
|
const s = ft();
|
|
8756
|
-
s.start(
|
|
9121
|
+
s.start(
|
|
9122
|
+
`Uploading ${groupsToUpload.length} secret${groupsToUpload.length === 1 ? "" : "s"}`
|
|
9123
|
+
);
|
|
8757
9124
|
let i = 0;
|
|
8758
|
-
for (const
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
project: projectChoice.name,
|
|
8773
|
-
environment: env2,
|
|
8774
|
-
key: aliasKey
|
|
8775
|
-
});
|
|
8776
|
-
if (alias2 === null) {
|
|
8777
|
-
failed.push({
|
|
8778
|
-
env: env2,
|
|
8779
|
-
name: e2.name,
|
|
8780
|
-
reason: `produced an invalid alias for project=${projectChoice.name} env=${env2} key=${aliasKey}`
|
|
8781
|
-
});
|
|
8782
|
-
} else {
|
|
8783
|
-
envUploaded.set(e2.name, alias2.literal);
|
|
8784
|
-
}
|
|
8785
|
-
} 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) {
|
|
8786
9139
|
failed.push({
|
|
8787
|
-
env:
|
|
8788
|
-
name:
|
|
8789
|
-
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}`
|
|
8790
9143
|
});
|
|
9144
|
+
} else {
|
|
9145
|
+
aliasByGroup.set(g.composite, alias2.literal);
|
|
8791
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
|
+
});
|
|
8792
9153
|
}
|
|
8793
9154
|
}
|
|
8794
9155
|
if (failed.length === 0) {
|
|
8795
|
-
s.stop(`Uploaded ${
|
|
9156
|
+
s.stop(`Uploaded ${groupsToUpload.length} secret${groupsToUpload.length === 1 ? "" : "s"}`);
|
|
8796
9157
|
} else {
|
|
8797
9158
|
s.error(
|
|
8798
|
-
`${
|
|
9159
|
+
`${groupsToUpload.length - failed.length}/${groupsToUpload.length} uploaded; ${failed.length} failed`
|
|
8799
9160
|
);
|
|
8800
9161
|
for (const f of failed) R2.warn(` [${f.env}] ${f.name}: ${f.reason}`);
|
|
8801
9162
|
}
|
|
8802
9163
|
}
|
|
8803
|
-
const
|
|
8804
|
-
const
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
);
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
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
|
+
}
|
|
8822
9185
|
}
|
|
8823
|
-
const
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
const envUploaded = uploadedByEnv.get(env2) ?? /* @__PURE__ */ new Map();
|
|
8828
|
-
const envLiterals = (perEnv.get(env2) ?? []).filter((e2) => !selected.has(`${env2}|${e2.name}`));
|
|
8829
|
-
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);
|
|
8830
9190
|
try {
|
|
8831
9191
|
const lines = composeKeynvEnv({
|
|
8832
|
-
uploadedAliases:
|
|
8833
|
-
literals:
|
|
8834
|
-
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
|
|
8835
9195
|
});
|
|
8836
|
-
writeFileSync(
|
|
9196
|
+
writeFileSync(targetPath, `${lines.join("\n")}
|
|
8837
9197
|
`);
|
|
9198
|
+
const relTarget = relFromRoot(root.path, targetPath);
|
|
9199
|
+
const total = b2.aliasLines.length + b2.literalEntries.length;
|
|
8838
9200
|
R2.success(
|
|
8839
|
-
|
|
9201
|
+
`${dirIntoExisting ? "Updated" : "Wrote"} ${relTarget} (${total} entr${total === 1 ? "y" : "ies"} from "${b2.envName}")`
|
|
8840
9202
|
);
|
|
8841
9203
|
} catch (err) {
|
|
8842
|
-
R2.
|
|
8843
|
-
`
|
|
9204
|
+
R2.error(
|
|
9205
|
+
`Failed to write ${relFromRoot(root.path, targetPath)}: ${err instanceof Error ? err.message : String(err)}`
|
|
8844
9206
|
);
|
|
9207
|
+
return { exitCode: 1 };
|
|
8845
9208
|
}
|
|
8846
9209
|
}
|
|
8847
9210
|
try {
|
|
@@ -8869,19 +9232,28 @@ ${detail}`
|
|
|
8869
9232
|
}
|
|
8870
9233
|
}
|
|
8871
9234
|
for (const f of envFiles) {
|
|
9235
|
+
const relSrc = relFromRoot(root.path, f.path);
|
|
8872
9236
|
try {
|
|
8873
|
-
|
|
8874
|
-
|
|
9237
|
+
const { renamedTo } = backupEnvFile(f.path);
|
|
9238
|
+
const relTarget = relFromRoot(root.path, renamedTo);
|
|
9239
|
+
R2.success(`Renamed ${relSrc} -> ${relTarget}`);
|
|
8875
9240
|
} catch (err) {
|
|
8876
|
-
R2.warn(`Could not
|
|
9241
|
+
R2.warn(`Could not rename ${relSrc}: ${err instanceof Error ? err.message : String(err)}`);
|
|
8877
9242
|
}
|
|
8878
9243
|
}
|
|
8879
9244
|
const otherEnvs = distinctEnvs.filter((e2) => e2 !== defaultEnv);
|
|
8880
9245
|
if (otherEnvs.length > 0) {
|
|
8881
9246
|
const lines = otherEnvs.map((env2) => {
|
|
8882
|
-
const
|
|
8883
|
-
|
|
8884
|
-
|
|
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>\`)`;
|
|
8885
9257
|
});
|
|
8886
9258
|
Se(lines.join("\n"), "Secrets in other envs");
|
|
8887
9259
|
}
|
|
@@ -8946,7 +9318,7 @@ async function pickFileEnvMapping(files, project) {
|
|
|
8946
9318
|
opts.push({ value: "__custom", label: "+ Custom env name\u2026" });
|
|
8947
9319
|
let envName = unwrap(
|
|
8948
9320
|
await Ee({
|
|
8949
|
-
message: `Map ${f
|
|
9321
|
+
message: `Map ${displayName(f)} to which keynv env?`,
|
|
8950
9322
|
options: opts,
|
|
8951
9323
|
initialValue: suggested
|
|
8952
9324
|
})
|
|
@@ -8963,43 +9335,6 @@ async function pickFileEnvMapping(files, project) {
|
|
|
8963
9335
|
}
|
|
8964
9336
|
return assignments;
|
|
8965
9337
|
}
|
|
8966
|
-
function parseAndMergePerEnv(mapping) {
|
|
8967
|
-
const acc = /* @__PURE__ */ new Map();
|
|
8968
|
-
for (const { file, envName } of mapping) {
|
|
8969
|
-
let entries;
|
|
8970
|
-
try {
|
|
8971
|
-
entries = parseEnvFile(readFileSync(file.path, "utf8"), file.path);
|
|
8972
|
-
} catch (err) {
|
|
8973
|
-
R2.warn(`${file.name}: ${err instanceof Error ? err.message : String(err)} \u2014 skipping file`);
|
|
8974
|
-
continue;
|
|
8975
|
-
}
|
|
8976
|
-
let envMap = acc.get(envName);
|
|
8977
|
-
if (!envMap) {
|
|
8978
|
-
envMap = /* @__PURE__ */ new Map();
|
|
8979
|
-
acc.set(envName, envMap);
|
|
8980
|
-
}
|
|
8981
|
-
for (const e2 of entries) {
|
|
8982
|
-
const existing = envMap.get(e2.name);
|
|
8983
|
-
if (existing) {
|
|
8984
|
-
existing.shadowedBy.push(file.name);
|
|
8985
|
-
existing.value = e2.value;
|
|
8986
|
-
existing.isAlias = e2.isAlias;
|
|
8987
|
-
} else {
|
|
8988
|
-
envMap.set(e2.name, {
|
|
8989
|
-
name: e2.name,
|
|
8990
|
-
value: e2.value,
|
|
8991
|
-
isAlias: e2.isAlias,
|
|
8992
|
-
source: file.name,
|
|
8993
|
-
sourceLine: e2.line,
|
|
8994
|
-
shadowedBy: []
|
|
8995
|
-
});
|
|
8996
|
-
}
|
|
8997
|
-
}
|
|
8998
|
-
}
|
|
8999
|
-
const out = /* @__PURE__ */ new Map();
|
|
9000
|
-
for (const [env2, m] of acc) out.set(env2, [...m.values()]);
|
|
9001
|
-
return out;
|
|
9002
|
-
}
|
|
9003
9338
|
function pickDefaultEnv(envs) {
|
|
9004
9339
|
if (envs.length === 0) return "dev";
|
|
9005
9340
|
if (envs.includes("dev")) return "dev";
|
|
@@ -9110,6 +9445,8 @@ var init_init = __esm({
|
|
|
9110
9445
|
init_dist();
|
|
9111
9446
|
init_envFile();
|
|
9112
9447
|
init_ai_context();
|
|
9448
|
+
init_backup();
|
|
9449
|
+
init_collision();
|
|
9113
9450
|
init_detect();
|
|
9114
9451
|
init_heuristics();
|
|
9115
9452
|
init_script_wrap();
|
|
@@ -9598,7 +9935,7 @@ var require_lib2 = __commonJS({
|
|
|
9598
9935
|
return `'${escapedValue}${value.slice(chunkIndex)}'`;
|
|
9599
9936
|
return `'${escapedValue}'`;
|
|
9600
9937
|
};
|
|
9601
|
-
var
|
|
9938
|
+
var pad22 = (value) => value < 10 ? "0" + value : "" + value;
|
|
9602
9939
|
var pad3 = (value) => value < 10 ? "00" + value : value < 100 ? "0" + value : "" + value;
|
|
9603
9940
|
var pad4 = (value) => value < 10 ? "000" + value : value < 100 ? "00" + value : value < 1e3 ? "0" + value : "" + value;
|
|
9604
9941
|
var convertTimezone = (tz) => {
|
|
@@ -9641,7 +9978,7 @@ var require_lib2 = __commonJS({
|
|
|
9641
9978
|
second = adjustedDate.getUTCSeconds();
|
|
9642
9979
|
millisecond = adjustedDate.getUTCMilliseconds();
|
|
9643
9980
|
}
|
|
9644
|
-
return escapeString(pad4(year) + "-" +
|
|
9981
|
+
return escapeString(pad4(year) + "-" + pad22(month) + "-" + pad22(day) + " " + pad22(hour) + ":" + pad22(minute) + ":" + pad22(second) + "." + pad3(millisecond));
|
|
9645
9982
|
};
|
|
9646
9983
|
exports.dateToString = dateToString;
|
|
9647
9984
|
var escapeId = (value, forbidQualified) => {
|
|
@@ -46559,6 +46896,8 @@ init_dist();
|
|
|
46559
46896
|
init_http();
|
|
46560
46897
|
init_envFile();
|
|
46561
46898
|
init_ai_context();
|
|
46899
|
+
init_backup();
|
|
46900
|
+
init_collision();
|
|
46562
46901
|
init_detect();
|
|
46563
46902
|
init_heuristics();
|
|
46564
46903
|
init_init();
|
|
@@ -46712,12 +47051,50 @@ var ProjectDeleteCommand = class extends Command {
|
|
|
46712
47051
|
// src/commands/init.ts
|
|
46713
47052
|
function toAliasKey2(name) {
|
|
46714
47053
|
if (!name) return name;
|
|
46715
|
-
const
|
|
46716
|
-
if (
|
|
47054
|
+
const KEY_RE4 = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
47055
|
+
if (KEY_RE4.test(name)) return name;
|
|
46717
47056
|
const normalised = name.toLowerCase().replace(/_/g, "-");
|
|
46718
|
-
if (
|
|
47057
|
+
if (KEY_RE4.test(normalised)) return normalised;
|
|
46719
47058
|
return normalised.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64) || "key";
|
|
46720
47059
|
}
|
|
47060
|
+
function escapeRegExp2(value) {
|
|
47061
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
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
|
+
}
|
|
47079
|
+
function writeKeynvEnvMappings(path, projectName, envName, secrets) {
|
|
47080
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
47081
|
+
const lines = [];
|
|
47082
|
+
for (const s of secrets) {
|
|
47083
|
+
const hasEntry = new RegExp(`^${escapeRegExp2(s.name)}=`, "m").test(existing);
|
|
47084
|
+
if (!hasEntry) {
|
|
47085
|
+
lines.push(`# ${s.name}`, `${s.name}=@${projectName}.${envName}.${s.aliasKey}`);
|
|
47086
|
+
}
|
|
47087
|
+
}
|
|
47088
|
+
if (lines.length === 0) return 0;
|
|
47089
|
+
const prefix = existing.trimEnd();
|
|
47090
|
+
const block = `# Generated by keynv setup
|
|
47091
|
+
${lines.join("\n")}
|
|
47092
|
+
`;
|
|
47093
|
+
writeFileSync(path, prefix ? `${prefix}
|
|
47094
|
+
|
|
47095
|
+
${block}` : block);
|
|
47096
|
+
return lines.length / 2;
|
|
47097
|
+
}
|
|
46721
47098
|
var InitCommand = class extends Command {
|
|
46722
47099
|
static paths = [["init"]];
|
|
46723
47100
|
static usage = Command.Usage({
|
|
@@ -46874,12 +47251,33 @@ any prompts.
|
|
|
46874
47251
|
return 0;
|
|
46875
47252
|
}
|
|
46876
47253
|
const secretsWithKeys = secrets.map((s) => ({ ...s, aliasKey: toAliasKey2(s.name) }));
|
|
46877
|
-
|
|
47254
|
+
const result = await this.uploadSecrets(
|
|
47255
|
+
client,
|
|
47256
|
+
projectId2,
|
|
47257
|
+
projectName,
|
|
47258
|
+
envName,
|
|
47259
|
+
secretsWithKeys
|
|
47260
|
+
);
|
|
47261
|
+
if (result !== 0) return result;
|
|
47262
|
+
const keynvEnvPath = join(process.cwd(), ".keynv.env");
|
|
47263
|
+
const written = writeKeynvEnvMappings(keynvEnvPath, projectName, envName, secretsWithKeys);
|
|
47264
|
+
this.context.stdout.write(
|
|
47265
|
+
written > 0 ? `keynv: wrote ${keynvEnvPath}
|
|
47266
|
+
` : `keynv: ${keynvEnvPath} already up to date
|
|
47267
|
+
`
|
|
47268
|
+
);
|
|
47269
|
+
return 0;
|
|
46878
47270
|
}
|
|
46879
47271
|
/**
|
|
46880
|
-
* Auto-scan mode (--yes without explicit --env-file).
|
|
46881
|
-
*
|
|
46882
|
-
*
|
|
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.
|
|
46883
47281
|
*/
|
|
46884
47282
|
async runAutoScan(client) {
|
|
46885
47283
|
const root = findProjectRoot(process.cwd());
|
|
@@ -46887,75 +47285,207 @@ any prompts.
|
|
|
46887
47285
|
this.context.stderr.write("keynv: no project root found (no package.json, .git, etc.).\n");
|
|
46888
47286
|
return 1;
|
|
46889
47287
|
}
|
|
46890
|
-
const envFiles =
|
|
47288
|
+
const envFiles = findEnvFilesRecursive(root.path);
|
|
46891
47289
|
if (envFiles.length === 0) {
|
|
46892
|
-
this.context.stdout.write(
|
|
47290
|
+
this.context.stdout.write(
|
|
47291
|
+
"keynv: no .env files found (scanned root and subdirectories). Nothing to migrate.\n"
|
|
47292
|
+
);
|
|
46893
47293
|
return 0;
|
|
46894
47294
|
}
|
|
46895
47295
|
const projectName = this.project ?? root.suggestedName;
|
|
46896
|
-
const
|
|
46897
|
-
const
|
|
47296
|
+
const cliEnv = this.env;
|
|
47297
|
+
const allSources = [];
|
|
47298
|
+
const isSecret = /* @__PURE__ */ new Set();
|
|
47299
|
+
let skippedCount = 0;
|
|
46898
47300
|
for (const f of envFiles) {
|
|
46899
47301
|
try {
|
|
46900
47302
|
const entries = parseEnvFile(readFileSync(f.path, "utf8"), f.path);
|
|
47303
|
+
const envName = cliEnv ?? suggestedEnvForSuffix(f.suffix);
|
|
46901
47304
|
for (const e2 of entries) {
|
|
46902
|
-
|
|
46903
|
-
|
|
47305
|
+
const verdict = classifyEntry(e2.name, e2.value).verdict;
|
|
47306
|
+
if (verdict === "skip") {
|
|
47307
|
+
skippedCount++;
|
|
47308
|
+
continue;
|
|
47309
|
+
}
|
|
47310
|
+
if (verdict === "secret" && !e2.isAlias) {
|
|
47311
|
+
isSecret.add(`${f.path}|${e2.name}`);
|
|
46904
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
|
+
});
|
|
46905
47321
|
}
|
|
46906
47322
|
} catch {
|
|
46907
|
-
this.context.stderr.write(
|
|
46908
|
-
`)
|
|
47323
|
+
this.context.stderr.write(
|
|
47324
|
+
`keynv: warning \u2014 could not parse ${displayHit(root.path, f)}, skipping.
|
|
47325
|
+
`
|
|
47326
|
+
);
|
|
46909
47327
|
}
|
|
46910
47328
|
}
|
|
46911
|
-
if (
|
|
46912
|
-
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");
|
|
46913
47331
|
return 0;
|
|
46914
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;
|
|
46915
47357
|
this.context.stdout.write(
|
|
46916
|
-
`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"}` : ""}.
|
|
46917
47359
|
`
|
|
46918
47360
|
);
|
|
46919
47361
|
if (this.dryRun) {
|
|
46920
|
-
for (const
|
|
46921
|
-
const
|
|
46922
|
-
|
|
46923
|
-
|
|
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
|
+
);
|
|
46924
47369
|
}
|
|
46925
47370
|
return 0;
|
|
46926
47371
|
}
|
|
46927
47372
|
let projectId2;
|
|
46928
47373
|
try {
|
|
46929
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
|
+
}
|
|
46930
47393
|
} catch {
|
|
46931
47394
|
const created = await client.request("/v1/projects", {
|
|
46932
47395
|
method: "POST",
|
|
46933
47396
|
body: {
|
|
46934
47397
|
name: projectName,
|
|
46935
|
-
environments:
|
|
47398
|
+
environments: distinctEnvs.map((name) => envBodyFor2(name))
|
|
46936
47399
|
}
|
|
46937
47400
|
});
|
|
46938
47401
|
projectId2 = created.id;
|
|
46939
|
-
this.context.stdout.write(
|
|
46940
|
-
`)
|
|
47402
|
+
this.context.stdout.write(
|
|
47403
|
+
`keynv: created project "${projectName}" (${projectId2}) with env(s): ${distinctEnvs.join(", ")}
|
|
47404
|
+
`
|
|
47405
|
+
);
|
|
46941
47406
|
}
|
|
46942
|
-
const
|
|
46943
|
-
const
|
|
46944
|
-
|
|
46945
|
-
|
|
46946
|
-
|
|
46947
|
-
|
|
46948
|
-
|
|
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
|
+
}
|
|
47441
|
+
}
|
|
47442
|
+
this.context.stdout.write(
|
|
47443
|
+
`keynv: uploaded ${uploaded}/${secretGroupMap.size} secret(s) to ${projectName}.
|
|
47444
|
+
`
|
|
46949
47445
|
);
|
|
46950
|
-
if (
|
|
46951
|
-
|
|
46952
|
-
${s.name}=@${projectName}.${envName}.${s.aliasKey}`).join("\n");
|
|
46953
|
-
const keynvEnvPath = join(root.path, ".keynv.env");
|
|
46954
|
-
writeFileSync(keynvEnvPath, `# Auto-generated by keynv init --yes
|
|
46955
|
-
${aliasLines}
|
|
47446
|
+
if (failed.length > 0) {
|
|
47447
|
+
for (const f of failed) this.context.stderr.write(` failed: ${f.name} \u2014 ${f.reason}
|
|
46956
47448
|
`);
|
|
46957
|
-
|
|
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")}
|
|
46958
47483
|
`);
|
|
47484
|
+
this.context.stdout.write(
|
|
47485
|
+
`keynv: ${dirIntoExisting ? "updated" : "wrote"} ${relFromRootPath(root.path, targetPath)}
|
|
47486
|
+
`
|
|
47487
|
+
);
|
|
47488
|
+
}
|
|
46959
47489
|
try {
|
|
46960
47490
|
const outcome = writeAiContext(root.path);
|
|
46961
47491
|
if (outcome === "created") this.context.stdout.write("keynv: wrote AGENTS.md\n");
|
|
@@ -46963,9 +47493,19 @@ ${aliasLines}
|
|
|
46963
47493
|
this.context.stdout.write("keynv: warning \u2014 could not write AGENTS.md.\n");
|
|
46964
47494
|
}
|
|
46965
47495
|
for (const f of envFiles) {
|
|
46966
|
-
|
|
46967
|
-
|
|
46968
|
-
|
|
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
|
+
}
|
|
46969
47509
|
}
|
|
46970
47510
|
this.context.stdout.write(
|
|
46971
47511
|
"keynv: done. Use `keynv exec` to run commands with resolved secrets.\n"
|
|
@@ -48537,28 +49077,27 @@ cli.register(RedactStreamCommand);
|
|
|
48537
49077
|
cli.register(TestCommand);
|
|
48538
49078
|
cli.register(ServerInitCommand);
|
|
48539
49079
|
var argv = process.argv.slice(2);
|
|
48540
|
-
|
|
48541
|
-
|
|
48542
|
-
|
|
48543
|
-
|
|
48544
|
-
|
|
48545
|
-
|
|
48546
|
-
|
|
48547
|
-
process.exit(1);
|
|
48548
|
-
});
|
|
48549
|
-
} else {
|
|
49080
|
+
async function main() {
|
|
49081
|
+
if (argv.length === 0) {
|
|
49082
|
+
const { runMenu: runMenu2 } = await Promise.resolve().then(() => (init_menu(), menu_exports));
|
|
49083
|
+
const { isInteractive: isInteractive2 } = await Promise.resolve().then(() => (init_tty(), tty_exports));
|
|
49084
|
+
if (isInteractive2()) {
|
|
49085
|
+
return runMenu2();
|
|
49086
|
+
}
|
|
48550
49087
|
process.stdout.write(
|
|
48551
49088
|
"keynv \u2014 AI-safe secrets management.\nRun `keynv --help` for the full command list, or run `keynv` in an interactive\nterminal to open the menu.\n"
|
|
48552
49089
|
);
|
|
48553
|
-
|
|
49090
|
+
return 0;
|
|
48554
49091
|
}
|
|
48555
|
-
|
|
48556
|
-
cli.run(argv).then((code) => process.exit(code ?? 0)).catch((err) => {
|
|
48557
|
-
process.stderr.write(`${fmtError(err)}
|
|
48558
|
-
`);
|
|
48559
|
-
process.exit(1);
|
|
48560
|
-
});
|
|
49092
|
+
return cli.run(argv);
|
|
48561
49093
|
}
|
|
49094
|
+
main().then((code) => {
|
|
49095
|
+
process.exitCode = code ?? 0;
|
|
49096
|
+
}).catch((err) => {
|
|
49097
|
+
process.stderr.write(`${fmtError(err)}
|
|
49098
|
+
`);
|
|
49099
|
+
process.exitCode = 1;
|
|
49100
|
+
});
|
|
48562
49101
|
/*! Bundled license information:
|
|
48563
49102
|
|
|
48564
49103
|
long/umd/index.js:
|