@shopify/cli-kit
Version:
A set of utilities, interfaces, and models that are common across all the platform features
174 lines • 5.92 kB
JavaScript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable jsdoc/require-throws */
/* eslint-disable no-restricted-imports */
import { outputDebug } from './output.js';
import { exec, spawn } from 'child_process';
/**
* Kills the process that calls the method and all its children.
*
* @param pid - Pid of the process to kill.
* @param killSignal - Type of kill signal to be used.
* @param killRoot - Whether to kill the root process.
* @param callback - Optional callback to run after killing the processes.
*/
export function treeKill(pid = process.pid, killSignal = 'SIGTERM', killRoot = true, callback) {
const after = callback ??
((error) => {
if (error)
outputDebug(`Failed to kill process ${pid}: ${error}`);
});
adaptedTreeKill(pid, killSignal, killRoot, after);
}
/**
* Adapted from https://github.com/pkrumins/node-tree-kill.
*
* Our implementation allows to skip killing the root process. This is useful for example when
* gracefully exiting the 'dev' process, as the default tree-kill implementation will cause it
* to exit with a non-zero code. Instead, we want to treat it as a graceful termination.
* In addition, we also add debug logging for better visibility in what tree-kill is doing.
*
* @param pid - Pid of the process to kill.
* @param killSignal - Type of kill signal to be used.
* @param killRoot - Whether to kill the root process.
* @param callback - Optional callback to run after killing the processes.
*/
function adaptedTreeKill(pid, killSignal, killRoot, callback) {
const rootPid = typeof pid === 'number' ? pid.toString() : pid;
if (Number.isNaN(rootPid)) {
if (callback) {
callback(new Error('pid must be a number'));
return;
}
else {
throw new Error('pid must be a number');
}
}
// A map from parent pid to an array of children pids
const tree = {};
tree[rootPid] = [];
// A set of pids to visit. We use it to recursively find all the children pids
const pidsToProcess = new Set();
pidsToProcess.add(rootPid);
switch (process.platform) {
case 'win32':
// @ts-ignore
exec(`taskkill /pid ${pid} /T /F`, callback);
break;
case 'darwin':
buildProcessTree(rootPid, tree, pidsToProcess, function (parentPid) {
return spawn('pgrep', ['-lfP', parentPid]);
}, function () {
killAll(tree, killSignal, rootPid, killRoot, callback);
});
break;
// Linux
default:
buildProcessTree(rootPid, tree, pidsToProcess, function (parentPid) {
return spawn('ps', ['-o', 'pid command', '--no-headers', '--ppid', parentPid]);
}, function () {
killAll(tree, killSignal, rootPid, killRoot, callback);
});
break;
}
}
/**
* Kills all processes in the process tree.
*
* @param tree - Process tree.
* @param killSignal - Type of kill signal to be used.
* @param rootPid - Pid of the root process.
* @param killRoot - Whether to kill the root process.
* @param callback - Optional callback to run after killing the processes.
*/
function killAll(tree, killSignal, rootPid, killRoot, callback) {
const killed = new Set();
try {
Object.keys(tree).forEach(function (pid) {
tree[pid].forEach(function (pidpid) {
if (!killed.has(pidpid)) {
killPid(pidpid, killSignal);
killed.add(pidpid);
}
});
if (pid === rootPid && killRoot && !killed.has(pid)) {
killPid(pid, killSignal);
killed.add(pid);
}
});
}
catch (err) {
if (callback) {
// @ts-ignore
callback(err);
return;
}
else {
throw err;
}
}
if (callback) {
callback();
}
}
/**
* Kills a process.
*
* @param pid - Pid of the process to kill.
* @param killSignal - Type of kill signal to be used.
*/
function killPid(pid, killSignal) {
try {
process.kill(parseInt(pid, 10), killSignal);
}
catch (err) {
// @ts-ignore
if (err.code !== 'ESRCH')
throw err;
}
}
/**
* Builds a process tree.
*
* @param parentPid - Pid of the parent process.
* @param tree - Process tree.
* @param pidsToProcess - Pids to process.
* @param spawnChildProcessesList - Function to spawn child processes.
* @param cb - Callback to run after building the process tree.
*/
function buildProcessTree(parentPid, tree, pidsToProcess, spawnChildProcessesList, cb) {
const ps = spawnChildProcessesList(parentPid);
let allData = '';
ps.stdout?.on('data', function (data) {
const dataStr = data.toString('ascii');
allData += dataStr;
});
const onClose = (code) => {
pidsToProcess.delete(parentPid);
if (code !== 0) {
// no more parent processes
if (pidsToProcess.size === 0) {
cb();
return;
}
return;
}
allData
.trim()
.split('\n')
.forEach(function (line) {
const match = line.match(/^(\d+)\s(.*)$/);
if (match) {
const pid = match[1];
const cmd = match[2];
tree[parentPid].push(pid);
tree[pid] = [];
outputDebug(`Killing process ${pid}: ${cmd}`);
pidsToProcess.add(pid);
buildProcessTree(pid, tree, pidsToProcess, spawnChildProcessesList, cb);
}
});
};
ps.on('close', onClose);
}
//# sourceMappingURL=tree-kill.js.map