@earthlink/dotvault 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +96 -24
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
package/dist/index.js
CHANGED
|
@@ -246,7 +246,7 @@ async function loadProjectConfig(cwd = process.cwd()) {
|
|
|
246
246
|
const cfg = await loadProjectConfigIfPresent(cwd);
|
|
247
247
|
if (cfg === null) {
|
|
248
248
|
throw new ValidationError(
|
|
249
|
-
`No project-config.json found at ${path}. Run \`
|
|
249
|
+
`No project-config.json found at ${path}. Run \`dotvault init\` here, or run dotvault from a directory that has one.`
|
|
250
250
|
);
|
|
251
251
|
}
|
|
252
252
|
return cfg;
|
|
@@ -287,7 +287,7 @@ function parseProjectConfig(raw, source = "<inline>") {
|
|
|
287
287
|
const apiUrl = proxyRaw?.apiUrl;
|
|
288
288
|
if (typeof apiUrl !== "string" || apiUrl.length === 0) {
|
|
289
289
|
throw new ValidationError(
|
|
290
|
-
`${source}: 'setup.bootstrap.proxy.apiUrl' is required for
|
|
290
|
+
`${source}: 'setup.bootstrap.proxy.apiUrl' is required for dotvault CLI`
|
|
291
291
|
);
|
|
292
292
|
}
|
|
293
293
|
const envSyncRaw = parsed.setup?.envSync;
|
|
@@ -349,6 +349,9 @@ function parseProjectConfig(raw, source = "<inline>") {
|
|
|
349
349
|
function resolveEnv(flag, appEnvVar, defaultEnv) {
|
|
350
350
|
return flag ?? appEnvVar ?? defaultEnv;
|
|
351
351
|
}
|
|
352
|
+
function envVarOverride(env = process.env) {
|
|
353
|
+
return env.DOTVAULT_ENV ?? env.APP_ENV;
|
|
354
|
+
}
|
|
352
355
|
|
|
353
356
|
// src/http.ts
|
|
354
357
|
function createHttpClient(apiUrl, fetchImpl = fetch) {
|
|
@@ -380,7 +383,7 @@ function createHttpClient(apiUrl, fetchImpl = fetch) {
|
|
|
380
383
|
method: "GET",
|
|
381
384
|
headers: jsonHeaders(token)
|
|
382
385
|
});
|
|
383
|
-
return handleResponse(res);
|
|
386
|
+
return handleResponse(res, { project: query.project, env: query.env });
|
|
384
387
|
},
|
|
385
388
|
async envPullAll(token, query) {
|
|
386
389
|
const url = new URL(`${base}/env/pull`);
|
|
@@ -390,7 +393,7 @@ function createHttpClient(apiUrl, fetchImpl = fetch) {
|
|
|
390
393
|
method: "GET",
|
|
391
394
|
headers: jsonHeaders(token)
|
|
392
395
|
});
|
|
393
|
-
return handleResponse(res);
|
|
396
|
+
return handleResponse(res, { project: query.project, env: query.env });
|
|
394
397
|
},
|
|
395
398
|
async envList(token, query) {
|
|
396
399
|
const url = new URL(`${base}/env/list`);
|
|
@@ -399,7 +402,7 @@ function createHttpClient(apiUrl, fetchImpl = fetch) {
|
|
|
399
402
|
method: "GET",
|
|
400
403
|
headers: jsonHeaders(token)
|
|
401
404
|
});
|
|
402
|
-
return handleResponse(res);
|
|
405
|
+
return handleResponse(res, { project: query.project });
|
|
403
406
|
},
|
|
404
407
|
async envKeysPut(token, query, body) {
|
|
405
408
|
const url = writeUrl(base, "/env/keys", query);
|
|
@@ -429,7 +432,7 @@ function createHttpClient(apiUrl, fetchImpl = fetch) {
|
|
|
429
432
|
method: "GET",
|
|
430
433
|
headers: jsonHeaders(token)
|
|
431
434
|
});
|
|
432
|
-
return handleResponse(res);
|
|
435
|
+
return handleResponse(res, { project: query.project, env: query.env });
|
|
433
436
|
},
|
|
434
437
|
async envRollback(token, query, body) {
|
|
435
438
|
const url = writeUrl(base, "/env/rollback", query);
|
|
@@ -459,7 +462,7 @@ function createHttpClient(apiUrl, fetchImpl = fetch) {
|
|
|
459
462
|
method: "GET",
|
|
460
463
|
headers: jsonHeaders(token)
|
|
461
464
|
});
|
|
462
|
-
return handleResponse(res);
|
|
465
|
+
return handleResponse(res, { project: query.project, env: query.env });
|
|
463
466
|
},
|
|
464
467
|
async envGlobalKeys(token, query, opts) {
|
|
465
468
|
const url = new URL(`${base}/env/global-keys`);
|
|
@@ -580,7 +583,7 @@ async function callFetch(fetchImpl, url, init) {
|
|
|
580
583
|
);
|
|
581
584
|
}
|
|
582
585
|
}
|
|
583
|
-
async function handleResponse(res) {
|
|
586
|
+
async function handleResponse(res, ctx) {
|
|
584
587
|
if (res.ok) {
|
|
585
588
|
return await res.json();
|
|
586
589
|
}
|
|
@@ -604,21 +607,90 @@ async function handleResponse(res) {
|
|
|
604
607
|
throw new AuthRequiredError(message);
|
|
605
608
|
}
|
|
606
609
|
if (res.status === 403) {
|
|
607
|
-
throw new PermissionDeniedError(
|
|
610
|
+
throw new PermissionDeniedError(
|
|
611
|
+
ctx?.project ?? "(unknown project)",
|
|
612
|
+
ctx?.env ?? "(unknown env)"
|
|
613
|
+
);
|
|
608
614
|
}
|
|
609
615
|
if (res.status === 404) {
|
|
610
|
-
throw new ProjectNotFoundError(
|
|
616
|
+
throw new ProjectNotFoundError(
|
|
617
|
+
ctx?.project ?? "(unknown project)",
|
|
618
|
+
ctx?.env ?? "(unknown env)"
|
|
619
|
+
);
|
|
611
620
|
}
|
|
612
621
|
throw new EnvSyncError(code, message, 99);
|
|
613
622
|
}
|
|
614
623
|
|
|
615
624
|
// src/keychain.ts
|
|
625
|
+
import { promises as fs } from "fs";
|
|
626
|
+
import { homedir } from "os";
|
|
627
|
+
import { join } from "path";
|
|
616
628
|
var SERVICE = "eln-env-sync";
|
|
629
|
+
function fallbackDir() {
|
|
630
|
+
const base = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
631
|
+
return join(base, "dotvault");
|
|
632
|
+
}
|
|
633
|
+
function fallbackPath() {
|
|
634
|
+
return join(fallbackDir(), "credentials.json");
|
|
635
|
+
}
|
|
636
|
+
async function readStore() {
|
|
637
|
+
try {
|
|
638
|
+
return JSON.parse(await fs.readFile(fallbackPath(), "utf8"));
|
|
639
|
+
} catch {
|
|
640
|
+
return {};
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
async function writeStore(store) {
|
|
644
|
+
await fs.mkdir(fallbackDir(), { recursive: true, mode: 448 });
|
|
645
|
+
await fs.writeFile(fallbackPath(), JSON.stringify(store), { mode: 384 });
|
|
646
|
+
}
|
|
647
|
+
var fileKeychain = {
|
|
648
|
+
async setPassword(service, account, password) {
|
|
649
|
+
const store = await readStore();
|
|
650
|
+
(store[service] ??= {})[account] = password;
|
|
651
|
+
await writeStore(store);
|
|
652
|
+
},
|
|
653
|
+
async getPassword(service, account) {
|
|
654
|
+
const store = await readStore();
|
|
655
|
+
return store[service]?.[account] ?? null;
|
|
656
|
+
},
|
|
657
|
+
async deletePassword(service, account) {
|
|
658
|
+
const store = await readStore();
|
|
659
|
+
if (store[service]?.[account] === void 0) return false;
|
|
660
|
+
delete store[service][account];
|
|
661
|
+
await writeStore(store);
|
|
662
|
+
return true;
|
|
663
|
+
},
|
|
664
|
+
async findCredentials(service) {
|
|
665
|
+
const store = await readStore();
|
|
666
|
+
return Object.entries(store[service] ?? {}).map(([account, password]) => ({
|
|
667
|
+
account,
|
|
668
|
+
password
|
|
669
|
+
}));
|
|
670
|
+
}
|
|
671
|
+
};
|
|
617
672
|
var keytarInstance;
|
|
673
|
+
var warnedFallback = false;
|
|
674
|
+
function warnFallbackOnce() {
|
|
675
|
+
if (warnedFallback) return;
|
|
676
|
+
warnedFallback = true;
|
|
677
|
+
process.stderr.write(
|
|
678
|
+
`dotvault: OS keychain unavailable (keytar could not load). Falling back to file-based token storage at ${fallbackPath()} (mode 0600).
|
|
679
|
+
On Linux, install libsecret (e.g. \`apt-get install libsecret-1-0\`) for the system keychain, or use a service token (DOTVAULT_TOKEN) for headless/CI mode.
|
|
680
|
+
`
|
|
681
|
+
);
|
|
682
|
+
}
|
|
618
683
|
async function getKeytar() {
|
|
619
684
|
if (keytarInstance === void 0) {
|
|
620
|
-
|
|
621
|
-
|
|
685
|
+
try {
|
|
686
|
+
const mod = await import("keytar");
|
|
687
|
+
const k = mod.default ?? mod;
|
|
688
|
+
await k.findCredentials(SERVICE);
|
|
689
|
+
keytarInstance = k;
|
|
690
|
+
} catch {
|
|
691
|
+
warnFallbackOnce();
|
|
692
|
+
keytarInstance = fileKeychain;
|
|
693
|
+
}
|
|
622
694
|
}
|
|
623
695
|
return keytarInstance;
|
|
624
696
|
}
|
|
@@ -868,7 +940,7 @@ import { resolve as resolve3, basename as basename2 } from "path";
|
|
|
868
940
|
|
|
869
941
|
// src/env-writer.ts
|
|
870
942
|
import { chmod, readFile as readFile2, rename, unlink, writeFile } from "fs/promises";
|
|
871
|
-
import { dirname, basename, join } from "path";
|
|
943
|
+
import { dirname, basename, join as join2 } from "path";
|
|
872
944
|
import { randomBytes } from "crypto";
|
|
873
945
|
var MARKER_BEGIN = "# BEGIN: eln-bootstrap-envsync (managed by env-sync)";
|
|
874
946
|
var MARKER_END = "# END: eln-bootstrap-envsync";
|
|
@@ -921,7 +993,7 @@ async function prepareRewrite(target, keys) {
|
|
|
921
993
|
const next = composeFile(outside, block);
|
|
922
994
|
const dir = dirname(target);
|
|
923
995
|
const tmpName = `.${basename(target)}.${randomBytes(6).toString("hex")}.tmp`;
|
|
924
|
-
const tmp =
|
|
996
|
+
const tmp = join2(dir, tmpName);
|
|
925
997
|
return {
|
|
926
998
|
target,
|
|
927
999
|
tmp,
|
|
@@ -979,7 +1051,7 @@ ${block}
|
|
|
979
1051
|
async function pullCommand(opts = {}) {
|
|
980
1052
|
const cfg = await loadProjectConfig(opts.cwd);
|
|
981
1053
|
const project = opts.project ?? cfg.name;
|
|
982
|
-
const env = resolveEnv(opts.env,
|
|
1054
|
+
const env = resolveEnv(opts.env, envVarOverride(), cfg.envSync.defaultEnv);
|
|
983
1055
|
const cwd = opts.cwd ?? process.cwd();
|
|
984
1056
|
const explicitFile = opts.file ?? (opts.target !== void 0 ? basename2(opts.target) : void 0);
|
|
985
1057
|
const http = opts.http ?? createHttpClient(cfg.proxy.apiUrl);
|
|
@@ -1070,7 +1142,7 @@ async function buildAuthedFetch2(http) {
|
|
|
1070
1142
|
import { spawn as nodeSpawn } from "child_process";
|
|
1071
1143
|
import { mkdtempSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1072
1144
|
import { tmpdir } from "os";
|
|
1073
|
-
import { basename as basename3, join as
|
|
1145
|
+
import { basename as basename3, join as join3, resolve as resolve4 } from "path";
|
|
1074
1146
|
async function runCommand(opts) {
|
|
1075
1147
|
if (opts.command === void 0 || opts.command.length === 0) {
|
|
1076
1148
|
throw new EnvSyncError(
|
|
@@ -1081,7 +1153,7 @@ async function runCommand(opts) {
|
|
|
1081
1153
|
}
|
|
1082
1154
|
const cfg = await loadProjectConfig(opts.cwd);
|
|
1083
1155
|
const project = opts.project ?? cfg.name;
|
|
1084
|
-
const env = resolveEnv(opts.env,
|
|
1156
|
+
const env = resolveEnv(opts.env, envVarOverride(), cfg.envSync.defaultEnv);
|
|
1085
1157
|
const http = opts.http ?? createHttpClient(cfg.proxy.apiUrl);
|
|
1086
1158
|
const authedFetch = await buildAuthedFetch3(http);
|
|
1087
1159
|
let merged = {};
|
|
@@ -1108,8 +1180,8 @@ async function runCommand(opts) {
|
|
|
1108
1180
|
).catch(() => null);
|
|
1109
1181
|
for (const f of fileResp?.files ?? []) {
|
|
1110
1182
|
if (f.pointerEnv !== void 0) {
|
|
1111
|
-
tmpDir ??= mkdtempSync(
|
|
1112
|
-
const p =
|
|
1183
|
+
tmpDir ??= mkdtempSync(join3(tmpdir(), "env-sync-files-"));
|
|
1184
|
+
const p = join3(tmpDir, basename3(f.name));
|
|
1113
1185
|
writeFileSync2(p, f.content, { mode: 384 });
|
|
1114
1186
|
childEnv[f.pointerEnv] = p;
|
|
1115
1187
|
} else {
|
|
@@ -1178,7 +1250,7 @@ import { basename as basename4, resolve as resolve5 } from "path";
|
|
|
1178
1250
|
async function resolveAuth(opts) {
|
|
1179
1251
|
const cfg = await loadProjectConfig(opts.cwd);
|
|
1180
1252
|
const project = opts.project ?? cfg.name;
|
|
1181
|
-
const env = resolveEnv(opts.env,
|
|
1253
|
+
const env = resolveEnv(opts.env, envVarOverride(), cfg.envSync.defaultEnv);
|
|
1182
1254
|
const http = opts.http ?? createHttpClient(cfg.proxy.apiUrl);
|
|
1183
1255
|
const serviceToken = resolveServiceToken();
|
|
1184
1256
|
if (serviceToken !== void 0 && serviceToken.length > 0) {
|
|
@@ -1242,7 +1314,7 @@ async function filesCommand(opts) {
|
|
|
1242
1314
|
|
|
1243
1315
|
// src/scan.ts
|
|
1244
1316
|
import { readdirSync, readFileSync, statSync } from "fs";
|
|
1245
|
-
import { join as
|
|
1317
|
+
import { join as join4, relative } from "path";
|
|
1246
1318
|
var RULES = [
|
|
1247
1319
|
{ id: "aws-access-key-id", description: "AWS Access Key ID", regex: /AKIA[0-9A-Z]{16}/g },
|
|
1248
1320
|
{
|
|
@@ -1331,7 +1403,7 @@ function scanPaths(paths, root = process.cwd()) {
|
|
|
1331
1403
|
if (st.isDirectory()) {
|
|
1332
1404
|
for (const entry of readdirSync(p)) {
|
|
1333
1405
|
if (SKIP_DIRS.has(entry)) continue;
|
|
1334
|
-
visit(
|
|
1406
|
+
visit(join4(p, entry));
|
|
1335
1407
|
}
|
|
1336
1408
|
return;
|
|
1337
1409
|
}
|
|
@@ -1355,7 +1427,7 @@ import { resolve as resolve6 } from "path";
|
|
|
1355
1427
|
async function resolveContext(opts) {
|
|
1356
1428
|
const cfg = await loadProjectConfig(opts.cwd);
|
|
1357
1429
|
const project = opts.project ?? cfg.name;
|
|
1358
|
-
const env = resolveEnv(opts.env,
|
|
1430
|
+
const env = resolveEnv(opts.env, envVarOverride(), cfg.envSync.defaultEnv);
|
|
1359
1431
|
const fileInput = opts.file ?? cfg.envSync.files[0];
|
|
1360
1432
|
const file = slugToFileName(fileNameToSlug(fileInput));
|
|
1361
1433
|
const http = opts.http ?? createHttpClient(cfg.proxy.apiUrl);
|
|
@@ -1688,7 +1760,7 @@ function buildProgram() {
|
|
|
1688
1760
|
"after",
|
|
1689
1761
|
`
|
|
1690
1762
|
Examples:
|
|
1691
|
-
$ dotvault login #
|
|
1763
|
+
$ dotvault login # email + password auth (Cognito)
|
|
1692
1764
|
$ dotvault pull # write .env / .npmrc etc. to local disk
|
|
1693
1765
|
$ dotvault run -- npm run dev # inject secrets into child process (no file written)
|
|
1694
1766
|
$ dotvault push .npmrc # add any file to the sync set
|