@seekrit/cli 0.23.2 → 0.23.3

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 +86 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from "node:child_process";
2
+ import { spawn, spawnSync } from "node:child_process";
3
3
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { z } from "zod";
5
5
  import { Command } from "commander";
@@ -1976,7 +1976,7 @@ function isServiceToken(value) {
1976
1976
  }
1977
1977
  //#endregion
1978
1978
  //#region package.json
1979
- var version = "0.23.2";
1979
+ var version = "0.23.3";
1980
1980
  //#endregion
1981
1981
  //#region ../../packages/api-client/src/index.ts
1982
1982
  var SeekritApiError = class extends Error {
@@ -4574,6 +4574,79 @@ async function materializeForRun(options) {
4574
4574
  };
4575
4575
  }
4576
4576
  }
4577
+ /**
4578
+ * Every live descendant of `root`, from a single `ps(1)` snapshot of the
4579
+ * process table. We follow the parent chain rather than the process group
4580
+ * because a process that calls setsid(2)/setpgid(2) leaves the group — nodemon
4581
+ * does exactly that for the script it watches — while the parent chain still
4582
+ * leads to it. Returns nothing if `ps` is unavailable; callers fall back to
4583
+ * signalling the immediate child.
4584
+ */
4585
+ function descendantsOf(root) {
4586
+ const ps = spawnSync("ps", [
4587
+ "-A",
4588
+ "-o",
4589
+ "pid=,ppid=,pgid="
4590
+ ], { encoding: "utf8" });
4591
+ if (ps.status !== 0 || typeof ps.stdout !== "string") return [];
4592
+ const childrenOf = /* @__PURE__ */ new Map();
4593
+ const groupOf = /* @__PURE__ */ new Map();
4594
+ const isId = (value) => value !== void 0 && Number.isInteger(value);
4595
+ for (const line of ps.stdout.split("\n")) {
4596
+ const [pid, ppid, pgid] = line.trim().split(/\s+/, 3).map(Number);
4597
+ if (!isId(pid) || !isId(ppid) || !isId(pgid)) continue;
4598
+ const siblings = childrenOf.get(ppid);
4599
+ if (siblings) siblings.push(pid);
4600
+ else childrenOf.set(ppid, [pid]);
4601
+ groupOf.set(pid, pgid);
4602
+ }
4603
+ const ourGroup = groupOf.get(process.pid);
4604
+ const found = [];
4605
+ const queue = [root];
4606
+ const seen = new Set(queue);
4607
+ for (let pid = queue.shift(); pid !== void 0; pid = queue.shift()) for (const kid of childrenOf.get(pid) ?? []) {
4608
+ if (seen.has(kid)) continue;
4609
+ seen.add(kid);
4610
+ found.push({
4611
+ pid: kid,
4612
+ escaped: ourGroup === void 0 || groupOf.get(kid) !== ourGroup
4613
+ });
4614
+ queue.push(kid);
4615
+ }
4616
+ return found;
4617
+ }
4618
+ /** Whether `pid` still exists — signal 0 tests reachability, delivers nothing. */
4619
+ function isAlive(pid) {
4620
+ try {
4621
+ process.kill(pid, 0);
4622
+ return true;
4623
+ } catch {
4624
+ return false;
4625
+ }
4626
+ }
4627
+ /** How long a signalled escapee gets to exit on its own before SIGKILL. */
4628
+ const ESCAPEE_GRACE_MS = 2e3;
4629
+ /**
4630
+ * Give already-signalled processes that left our group a moment to finish
4631
+ * shutting down, then SIGKILL the holdouts. We are the last process that knows
4632
+ * their pids: the tty won't hang them up, and once their parent exits they
4633
+ * reparent to init, out of reach of any walk down from our own child — which is
4634
+ * how one survives for days, invisible to `ps -a` for want of a tty. Only
4635
+ * processes that already ignored a termination signal are killed; the command
4636
+ * itself and anything still in our group shut down at their own pace.
4637
+ */
4638
+ async function reapEscapees(pids) {
4639
+ if (pids.size === 0) return;
4640
+ const deadline = Date.now() + ESCAPEE_GRACE_MS;
4641
+ let live = [...pids].filter(isAlive);
4642
+ while (live.length > 0 && Date.now() < deadline) {
4643
+ await new Promise((resolve) => setTimeout(resolve, 50));
4644
+ live = live.filter(isAlive);
4645
+ }
4646
+ for (const pid of live) try {
4647
+ process.kill(pid, "SIGKILL");
4648
+ } catch {}
4649
+ }
4577
4650
  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) => {
4578
4651
  const [cmd, ...args] = commandParts;
4579
4652
  if (!cmd) fail("no command given");
@@ -4588,8 +4661,7 @@ program.command("run").description("run a command with decrypted secrets injecte
4588
4661
  env: {
4589
4662
  ...values,
4590
4663
  ...process.env
4591
- },
4592
- detached: posix
4664
+ }
4593
4665
  });
4594
4666
  const signals = [
4595
4667
  "SIGINT",
@@ -4597,16 +4669,21 @@ program.command("run").description("run a command with decrypted secrets injecte
4597
4669
  "SIGHUP",
4598
4670
  "SIGQUIT"
4599
4671
  ];
4672
+ const escapees = /* @__PURE__ */ new Set();
4600
4673
  const forward = (signal) => {
4601
4674
  if (child.exitCode !== null || child.signalCode !== null) return;
4602
- if (posix && child.pid) try {
4603
- process.kill(-child.pid, signal);
4604
- return;
4605
- } catch {}
4606
4675
  child.kill(signal);
4676
+ if (!posix || !child.pid) return;
4677
+ for (const { pid, escaped } of descendantsOf(child.pid)) {
4678
+ if (escaped) escapees.add(pid);
4679
+ try {
4680
+ process.kill(pid, signal);
4681
+ } catch {}
4682
+ }
4607
4683
  };
4608
4684
  for (const signal of signals) process.on(signal, forward);
4609
- child.on("exit", (code, signal) => {
4685
+ child.on("exit", async (code, signal) => {
4686
+ await reapEscapees(escapees);
4610
4687
  for (const s of signals) process.off(s, forward);
4611
4688
  if (signal) process.kill(process.pid, signal);
4612
4689
  else process.exit(code ?? 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seekrit/cli",
3
- "version": "0.23.2",
3
+ "version": "0.23.3",
4
4
  "description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
5
5
  "type": "module",
6
6
  "publishConfig": {