@seekrit/cli 0.23.1 → 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.
- package/dist/index.js +88 -4
- package/package.json +3 -3
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.
|
|
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");
|
|
@@ -4582,6 +4655,7 @@ program.command("run").description("run a command with decrypted secrets injecte
|
|
|
4582
4655
|
for (const name of Object.keys(values)) if (process.env[name] !== void 0 && process.env[name] !== values[name]) provenance[name] = "env";
|
|
4583
4656
|
printExplain(provenance);
|
|
4584
4657
|
}
|
|
4658
|
+
const posix = process.platform !== "win32";
|
|
4585
4659
|
const child = spawn(cmd, args, {
|
|
4586
4660
|
stdio: "inherit",
|
|
4587
4661
|
env: {
|
|
@@ -4595,11 +4669,21 @@ program.command("run").description("run a command with decrypted secrets injecte
|
|
|
4595
4669
|
"SIGHUP",
|
|
4596
4670
|
"SIGQUIT"
|
|
4597
4671
|
];
|
|
4672
|
+
const escapees = /* @__PURE__ */ new Set();
|
|
4598
4673
|
const forward = (signal) => {
|
|
4599
|
-
if (child.exitCode
|
|
4674
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
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
|
+
}
|
|
4600
4683
|
};
|
|
4601
4684
|
for (const signal of signals) process.on(signal, forward);
|
|
4602
|
-
child.on("exit", (code, signal) => {
|
|
4685
|
+
child.on("exit", async (code, signal) => {
|
|
4686
|
+
await reapEscapees(escapees);
|
|
4603
4687
|
for (const s of signals) process.off(s, forward);
|
|
4604
4688
|
if (signal) process.kill(process.pid, signal);
|
|
4605
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.
|
|
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": {
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"@types/node": "^26.1.0",
|
|
28
28
|
"tsdown": "^0.22.3",
|
|
29
29
|
"@seekrit/api-client": "0.0.1",
|
|
30
|
-
"@seekrit/
|
|
31
|
-
"@seekrit/
|
|
30
|
+
"@seekrit/core": "0.0.1",
|
|
31
|
+
"@seekrit/crypto": "0.0.1"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsdown",
|