@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 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 \`env-sync init\` here, or run env-sync from a directory that has one.`
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 env-sync CLI`
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("unknown", "unknown");
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("unknown", "unknown");
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
- const mod = await import("keytar");
621
- keytarInstance = mod.default ?? mod;
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 = join(dir, tmpName);
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, process.env.APP_ENV, cfg.envSync.defaultEnv);
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 join2, resolve as resolve4 } from "path";
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, process.env.APP_ENV, cfg.envSync.defaultEnv);
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(join2(tmpdir(), "env-sync-files-"));
1112
- const p = join2(tmpDir, basename3(f.name));
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, process.env.APP_ENV, cfg.envSync.defaultEnv);
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 join3, relative } from "path";
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(join3(p, entry));
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, process.env.APP_ENV, cfg.envSync.defaultEnv);
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 # browser-based auth (Cognito)
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