process-ancestry
Version:
Cross-platform Node.js library for retrieving process ancestry information on Unix/Linux, macOS, and Windows
130 lines (126 loc) • 3.77 kB
JavaScript
// src/index.ts
import os from "os";
// src/lib/unix.ts
import { execSync } from "child_process";
function getProcessInfo(pid) {
try {
const output = execSync(`ps -p ${pid} -o pid=,ppid=,command=`, {
encoding: "utf8",
timeout: 5e3
// 5 second timeout
}).trim();
if (!output) {
return null;
}
const [pidStr, ppidStr, ...commandParts] = output.split(/\s+/);
const parsedPid = pidStr ? parseInt(pidStr, 10) : NaN;
const parsedPpid = ppidStr ? parseInt(ppidStr, 10) : NaN;
if (isNaN(parsedPid) || isNaN(parsedPpid)) {
return null;
}
return {
pid: parsedPid,
ppid: parsedPpid,
command: commandParts.join(" ") || void 0
};
} catch (error) {
if (error instanceof Error && error.message.includes("timeout")) {
console.warn(`Process lookup timed out for PID ${pid}`);
}
return null;
}
}
function getAncestryUnix(startPid) {
const result = [];
const visited = /* @__PURE__ */ new Set();
let currentPid = startPid;
let maxDepth = 1e3;
while (currentPid && maxDepth > 0) {
if (visited.has(currentPid)) {
console.warn(`Detected cycle in process tree at PID ${currentPid}`);
break;
}
visited.add(currentPid);
const info = getProcessInfo(currentPid);
if (!info || info.ppid === 0 || info.ppid === 1) break;
result.push(info);
currentPid = info.ppid;
maxDepth--;
}
if (maxDepth === 0) {
console.warn(`Reached maximum depth limit while traversing process tree from PID ${startPid}`);
}
return result;
}
// src/lib/windows.ts
import { execSync as execSync2 } from "child_process";
function getProcessInfo2(pid) {
try {
const output = execSync2(
`wmic process where (ProcessId=${pid}) get ProcessId,ParentProcessId,CommandLine /format:csv`,
{
encoding: "utf8",
timeout: 1e4
// 10 second timeout (wmic can be slower)
}
);
if (!output) {
return null;
}
const lines = output.split("\n").filter((line) => line.trim() && !line.startsWith("Node"));
const fields = lines.pop()?.split(",");
if (!fields || fields.length < 4) return null;
const [_node, commandLine, parentPid, thisPid] = fields;
const parsedPid = thisPid ? parseInt(thisPid.trim(), 10) : NaN;
const parsedPpid = parentPid ? parseInt(parentPid.trim(), 10) : NaN;
if (isNaN(parsedPid) || isNaN(parsedPpid)) {
return null;
}
return {
pid: parsedPid,
ppid: parsedPpid,
command: commandLine?.trim() || void 0
};
} catch (error) {
if (error instanceof Error && error.message.includes("timeout")) {
console.warn(`Process lookup timed out for PID ${pid}`);
}
return null;
}
}
function getAncestryWindows(startPid) {
const result = [];
const visited = /* @__PURE__ */ new Set();
let currentPid = startPid;
let maxDepth = 1e3;
while (currentPid && maxDepth > 0) {
if (visited.has(currentPid)) {
console.warn(`Detected cycle in process tree at PID ${currentPid}`);
break;
}
visited.add(currentPid);
const info = getProcessInfo2(currentPid);
if (!info || info.ppid === 0 || info.ppid === 4) break;
result.push(info);
currentPid = info.ppid;
maxDepth--;
}
if (maxDepth === 0) {
console.warn(`Reached maximum depth limit while traversing process tree from PID ${startPid}`);
}
return result;
}
// src/index.ts
function getProcessAncestry(pid = process.pid) {
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
throw new Error("PID must be a positive integer");
}
if (os.platform() === "win32") {
return getAncestryWindows(pid);
} else {
return getAncestryUnix(pid);
}
}
export {
getProcessAncestry
};