@seekrit/cli 0.1.0 → 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +59 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -315,7 +315,7 @@ async function unwrapDek(wrapped, privateKey) {
315
315
  }
316
316
  //#endregion
317
317
  //#region package.json
318
- var version = "0.1.0";
318
+ var version = "0.2.0";
319
319
  const PROJECT_FILE = "seekrit.json";
320
320
  function globalConfigPath() {
321
321
  return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "seekrit", "config.json");
@@ -549,7 +549,12 @@ async function readStdin() {
549
549
  }
550
550
  //#endregion
551
551
  //#region src/context.ts
552
- function buildContext() {
552
+ /**
553
+ * Build the client context from configured credentials, or return null when
554
+ * none are set. `seekrit run` uses this to degrade to a plain launcher instead
555
+ * of exiting; every other command goes through `buildContext`, which fails.
556
+ */
557
+ function tryBuildContext() {
553
558
  const config = readGlobalConfig();
554
559
  const apiUrl = process.env.SEEKRIT_API_URL ?? config.apiUrl ?? "http://localhost:8787";
555
560
  const token = process.env.SEEKRIT_TOKEN ?? config.token;
@@ -563,7 +568,7 @@ function buildContext() {
563
568
  type: "dev",
564
569
  email: devUser
565
570
  };
566
- else fail("no credentials found — run `seekrit login --token skt_…`, or set SEEKRIT_TOKEN / SEEKRIT_DEV_USER");
571
+ else return null;
567
572
  return {
568
573
  client: new SeekritClient({
569
574
  baseUrl: apiUrl,
@@ -572,6 +577,11 @@ function buildContext() {
572
577
  auth
573
578
  };
574
579
  }
580
+ function buildContext() {
581
+ const ctx = tryBuildContext();
582
+ if (!ctx) fail("no credentials found — run `seekrit login --token skt_…`, or set SEEKRIT_TOKEN / SEEKRIT_DEV_USER");
583
+ return ctx;
584
+ }
575
585
  function isTokenAuth(ctx) {
576
586
  return ctx.auth.type === "bearer" && isServiceToken(ctx.auth.token);
577
587
  }
@@ -682,21 +692,30 @@ async function materializeEnv(ctx, opts) {
682
692
  provenance[secret.name] = label;
683
693
  }
684
694
  }
685
- const loadedEnvFiles = [];
686
- for (const file of opts.envFiles) {
695
+ return {
696
+ values,
697
+ provenance,
698
+ scope,
699
+ loadedEnvFiles: overlayEnvFiles(values, provenance, opts.envFiles)
700
+ };
701
+ }
702
+ /**
703
+ * Overlay `.env` files onto an existing value/provenance set (later files win).
704
+ * Missing files are skipped. Returns the files that were actually loaded. Used
705
+ * both by {@link materializeEnv} and by `seekrit run`'s degraded path, where
706
+ * managed secrets are unavailable but `.env` should still apply.
707
+ */
708
+ function overlayEnvFiles(values, provenance, envFiles) {
709
+ const loaded = [];
710
+ for (const file of envFiles) {
687
711
  if (!existsSync(file)) continue;
688
- loadedEnvFiles.push(file);
712
+ loaded.push(file);
689
713
  for (const [name, value] of Object.entries(parseDotenv(readFileSync(file, "utf8")))) {
690
714
  values[name] = value;
691
715
  provenance[name] = `dotenv:${file}`;
692
716
  }
693
717
  }
694
- return {
695
- values,
696
- provenance,
697
- scope,
698
- loadedEnvFiles
699
- };
718
+ return loaded;
700
719
  }
701
720
  /** Print a name → source table to stderr (never the secret values). */
702
721
  function printExplain(provenance) {
@@ -990,14 +1009,39 @@ async function materialize(ctx, options) {
990
1009
  envFiles: options.envFile ?? [".env"]
991
1010
  });
992
1011
  }
1012
+ /**
1013
+ * Best-effort variant of {@link materialize} for `seekrit run`: if credentials
1014
+ * are missing or seekrit can't be reached (network, auth, or decryption
1015
+ * failure), log why and fall back to the `.env` overlay alone so the command
1016
+ * still runs. This keeps `seekrit run` working across environments that lack a
1017
+ * token or API — mirroring the `seekrit-run` launcher.
1018
+ */
1019
+ async function materializeForRun(options) {
1020
+ const envFiles = options.envFile ?? [".env"];
1021
+ try {
1022
+ const ctx = tryBuildContext();
1023
+ if (!ctx) throw new Error("no credentials found (set SEEKRIT_TOKEN / SEEKRIT_DEV_USER or run `seekrit login`)");
1024
+ return await materialize(ctx, options);
1025
+ } catch (err) {
1026
+ const message = err instanceof Error ? err.message : String(err);
1027
+ console.error(`seekrit: continuing without seekrit-managed secrets: ${message}`);
1028
+ const values = {};
1029
+ const provenance = {};
1030
+ overlayEnvFiles(values, provenance, envFiles);
1031
+ return {
1032
+ values,
1033
+ provenance
1034
+ };
1035
+ }
1036
+ }
993
1037
  program.command("run").description("run a command with decrypted secrets injected (process env > .env > app > group)").passThroughOptions().option("--org <slug>").option("--app <slug>", "application slug (token path infers this)").option("--env <slug>", "environment slug (token path infers this)").option("--with <group=env>", "override one group’s slice for this run", collectKv).option("--env-file <path>", "a .env file to overlay (repeatable; default .env)", collectList).option("--explain", "print where each variable resolved from (to stderr)").argument("<command...>", "command to run (prefix with -- to pass flags)").action(async (commandParts, options) => {
994
- const { values, provenance } = await materialize(buildContext(), options);
1038
+ const [cmd, ...args] = commandParts;
1039
+ if (!cmd) fail("no command given");
1040
+ const { values, provenance } = await materializeForRun(options);
995
1041
  if (options.explain) {
996
1042
  for (const name of Object.keys(values)) if (process.env[name] !== void 0 && process.env[name] !== values[name]) provenance[name] = "env";
997
1043
  printExplain(provenance);
998
1044
  }
999
- const [cmd, ...args] = commandParts;
1000
- if (!cmd) fail("no command given");
1001
1045
  const child = spawn(cmd, args, {
1002
1046
  stdio: "inherit",
1003
1047
  env: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seekrit/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
5
5
  "type": "module",
6
6
  "publishConfig": {