first commit

This commit is contained in:
Artem Anufrij
2026-10-07 18:56:27 +02:00
commit 2a8a60b725
7768 changed files with 1550851 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
import {spawn} from 'node:child_process';
/**
* Strips known-benign noise from a command's stderr.
* @param {string} stderr
* @returns {string|undefined}
*/
export function stripStderr(stderr) {
if (!stderr) return;
stderr = stderr.trim();
// Strip bogus screen size error.
// See https://github.com/microsoft/vscode/issues/98590
const regex = /your \d+x\d+ screen size is bogus\. expect trouble/gi;
stderr = stderr.replaceAll(regex, '');
return stderr.trim();
}
/**
* Spawn a binary and read its stdout.
* @param {string} cmd The name of the binary to spawn.
* @param {string[]} args The arguments for the binary.
* @param {object} [options] Optional options for the spawn function.
* @param {Function} done done(err, stdout, code)
*/
export function run(cmd, args, options, done) {
if (typeof options === 'function') {
done = options;
options = undefined;
}
let executed = false;
const child = spawn(cmd, args, options);
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => {
stdout += data.toString();
});
child.stderr.on('data', (data) => {
stderr += data.toString();
});
child.on('error', (error) => {
if (executed) return;
executed = true;
// Pass the original error through so callers can inspect err.code
// (e.g. 'ENOENT' when the binary is missing) to decide on a fallback.
done(error);
});
child.on('close', (code) => {
if (executed) return;
executed = true;
stderr = stripStderr(stderr);
if (stderr) {
done(new Error(stderr));
return;
}
done(null, stdout, code);
});
}
+64
View File
@@ -0,0 +1,64 @@
import os from 'node:os';
import {ps} from './ps.js';
import {wmic} from './wmic.js';
import {powershell} from './powershell.js';
const platformToMethod = {
darwin: 'ps',
sunos: 'ps',
freebsd: 'ps',
netbsd: 'ps',
win: 'win',
linux: 'ps',
aix: 'ps',
};
let platform = os.platform();
if (platform.startsWith('win')) {
platform = 'win';
}
const method = platformToMethod[platform];
/**
* Lists the system pids on Windows. wmic has been removed from recent versions
* (Windows 11 24H2, Windows Server 2025), so it is tried first (it is faster
* when present) and PowerShell is used as a fallback when it is missing.
* Exported for testing.
* @param {Function} callback callback(err, list)
* @param {Function} [wmicFn] Injectable wmic backend, used for testing.
* @param {Function} [powershellFn] Injectable PowerShell backend, for testing.
*/
export function getWindows(callback, wmicFn = wmic, powershellFn = powershell) {
wmicFn((error, list) => {
if (error && error.code === 'ENOENT') {
powershellFn(callback);
return;
}
callback(error, list);
});
}
/**
* Gets the list of all the pids of the system.
* @param {Function} callback Called when the list is ready.
*/
export function get(callback) {
if (method === undefined) {
callback(
new Error(
os.platform() +
' is not supported yet, please open an issue (https://github.com/simonepri/pidtree)',
),
);
return;
}
if (method === 'win') {
getWindows(callback);
return;
}
ps(callback);
}
+24
View File
@@ -0,0 +1,24 @@
/**
* Parses the `<ppid> <pid>` output produced by the ps/wmic/powershell backends
* into a list of `[ppid, pid]` pairs. The header row and any non-numeric lines
* are skipped, and all common line endings (\n, \r\n, \r\r\n) are handled.
* @param {string} stdout The raw command output.
* @returns {Array<[number, number]>}
*/
export function parse(stdout) {
const list = [];
for (const rawLine of stdout.split(/\r*\n/)) {
const line = rawLine.trim();
if (!line) continue;
const [rawPpid, rawPid] = line.split(/\s+/);
const ppid = Number.parseInt(rawPpid, 10);
const pid = Number.parseInt(rawPid, 10);
// Skips the header row and any stray output.
if (Number.isNaN(ppid) || Number.isNaN(pid)) continue;
list.push([ppid, pid]);
}
return list;
}
+94
View File
@@ -0,0 +1,94 @@
import {get} from './get.js';
/**
* Get the list of children and grandchildren pids of the given PID.
* @param {number|string} pid A PID. If -1 will return all the pids.
* @param {object} [options] Optional options object.
* @param {boolean} [options.root=false] Include the provided PID in the list.
* @param {boolean} [options.advanced=false] Returns a list of objects in the
* format {ppid: X, pid: Y}.
* @param {Function} callback callback(err, list) Called when the list is ready.
*/
export function pidtreeCallback(pid, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
if (typeof options !== 'object' || options === null) {
options = {};
}
pid = Number.parseInt(pid, 10);
if (Number.isNaN(pid) || pid < -1) {
callback(new TypeError('The pid provided is invalid'));
return;
}
get((error, list) => {
if (error) {
callback(error);
return;
}
// If the user wants the whole list just return it.
if (pid === -1) {
const all = list.map((entry) =>
options.advanced ? {ppid: entry[0], pid: entry[1]} : entry[1],
);
callback(null, all);
return;
}
let root;
for (const entry of list) {
if (entry[1] === pid) {
root = options.advanced ? {ppid: entry[0], pid} : pid;
break;
}
if (entry[0] === pid) {
// Special pids like 0 on *nix.
root = options.advanced ? {pid} : pid;
}
}
if (root === undefined) {
callback(new Error('No matching pid found'));
return;
}
// Build the adjacency Hash Map (pid -> [children of pid]).
const tree = {};
for (const [parentPid, childPid] of list) {
if (tree[parentPid]) {
tree[parentPid].push(childPid);
} else {
tree[parentPid] = [childPid];
}
}
// Starting from the PID provided by the user, traverse the tree using the
// adjacency Hash Map until the whole subtree is visited. Each pid
// encountered while visiting is added to the pids array.
const pids = [root];
let index = 0;
while (index < pids.length) {
const current = options.advanced ? pids[index].pid : pids[index];
index++;
const children = tree[current];
if (!children) continue;
for (const childPid of children) {
pids.push(options.advanced ? {ppid: current, pid: childPid} : childPid);
}
delete tree[current];
}
if (!options.root) {
pids.shift(); // Remove root.
}
callback(null, pids);
});
}
+51
View File
@@ -0,0 +1,51 @@
import {run as defaultRun} from './bin.js';
import {parse} from './parse.js';
// PowerShell command used to list every process as "<ppid> <pid>" lines.
// Get-CimInstance is the supported replacement for the removed wmic utility
// (see https://github.com/simonepri/pidtree/issues/20). $ProgressPreference is
// silenced so PowerShell does not serialize its progress stream to stderr.
const COMMAND =
"$ProgressPreference = 'SilentlyContinue'; " +
'Get-CimInstance -ClassName Win32_Process | ' +
'ForEach-Object { "$($_.ParentProcessId) $($_.ProcessId)" }';
// The command is passed through -EncodedCommand (Base64 of the UTF-16LE
// string) to avoid any cross-shell argument quoting issues on Windows.
const ENCODED = Buffer.from(COMMAND, 'utf16le').toString('base64');
/**
* Gets the list of all the pids of the system through PowerShell.
* Used as a fallback on Windows installations where wmic is not available
* (e.g. Windows 11 24H2 and Windows Server 2025).
* @param {Function} callback callback(err, list)
* @param {Function} [run] Injectable spawner, used for testing.
*/
export function powershell(callback, run = defaultRun) {
const args = ['-NoProfile', '-NonInteractive', '-EncodedCommand', ENCODED];
const options = {windowsHide: true};
// Example of stdout
//
// 0 777
// 777 778
run('powershell', args, options, (error, stdout, code) => {
if (error) {
callback(error);
return;
}
if (code !== 0) {
callback(
new Error('pidtree powershell command exited with code ' + code),
);
return;
}
try {
callback(null, parse(stdout));
} catch (error) {
callback(error);
}
});
}
+32
View File
@@ -0,0 +1,32 @@
import {run as defaultRun} from './bin.js';
import {parse} from './parse.js';
/**
* Gets the list of all the pids of the system through the ps command.
* @param {Function} callback callback(err, list)
* @param {Function} [run] Injectable spawner, used for testing.
*/
export function ps(callback, run = defaultRun) {
// Example of stdout
//
// PPID PID
// 1 430
// 430 432
run('ps', ['-A', '-o', 'ppid,pid'], (error, stdout, code) => {
if (error) {
callback(error);
return;
}
if (code !== 0) {
callback(new Error('pidtree ps command exited with code ' + code));
return;
}
try {
callback(null, parse(stdout));
} catch (error) {
callback(error);
}
});
}
+34
View File
@@ -0,0 +1,34 @@
import {run as defaultRun} from './bin.js';
import {parse} from './parse.js';
/**
* Gets the list of all the pids of the system through the wmic command.
* @param {Function} callback callback(err, list)
* @param {Function} [run] Injectable spawner, used for testing.
*/
export function wmic(callback, run = defaultRun) {
const args = ['PROCESS', 'get', 'ParentProcessId,ProcessId'];
const options = {windowsHide: true, windowsVerbatimArguments: true};
// Example of stdout
//
// ParentProcessId ProcessId
// 0 777
run('wmic', args, options, (error, stdout, code) => {
if (error) {
callback(error);
return;
}
if (code !== 0) {
callback(new Error('pidtree wmic command exited with code ' + code));
return;
}
try {
callback(null, parse(stdout));
} catch (error) {
callback(error);
}
});
}