chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import process from "node:process";
|
||||
|
||||
export function runCommand(command, args = [], options = {}) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: options.cwd,
|
||||
env: options.env,
|
||||
encoding: "utf8",
|
||||
input: options.input,
|
||||
maxBuffer: options.maxBuffer,
|
||||
stdio: options.stdio ?? "pipe",
|
||||
shell: options.shell ?? (process.platform === "win32" ? (process.env.SHELL || true) : false),
|
||||
windowsHide: true
|
||||
});
|
||||
|
||||
return {
|
||||
command,
|
||||
args,
|
||||
status: result.status ?? 0,
|
||||
signal: result.signal ?? null,
|
||||
stdout: result.stdout ?? "",
|
||||
stderr: result.stderr ?? "",
|
||||
error: result.error ?? null
|
||||
};
|
||||
}
|
||||
|
||||
export function runCommandChecked(command, args = [], options = {}) {
|
||||
const result = runCommand(command, args, options);
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
throw new Error(formatCommandFailure(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function binaryAvailable(command, versionArgs = ["--version"], options = {}) {
|
||||
const result = runCommand(command, versionArgs, options);
|
||||
if (result.error && /** @type {NodeJS.ErrnoException} */ (result.error).code === "ENOENT") {
|
||||
return { available: false, detail: "not found" };
|
||||
}
|
||||
if (result.error) {
|
||||
return { available: false, detail: result.error.message };
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
const detail = result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`;
|
||||
return { available: false, detail };
|
||||
}
|
||||
return { available: true, detail: result.stdout.trim() || result.stderr.trim() || "ok" };
|
||||
}
|
||||
|
||||
function looksLikeMissingProcessMessage(text) {
|
||||
return /not found|no running instance|cannot find|does not exist|no such process/i.test(text);
|
||||
}
|
||||
|
||||
export function terminateProcessTree(pid, options = {}) {
|
||||
if (!Number.isFinite(pid)) {
|
||||
return { attempted: false, delivered: false, method: null };
|
||||
}
|
||||
|
||||
const platform = options.platform ?? process.platform;
|
||||
const runCommandImpl = options.runCommandImpl ?? runCommand;
|
||||
const killImpl = options.killImpl ?? process.kill.bind(process);
|
||||
|
||||
if (platform === "win32") {
|
||||
const result = runCommandImpl("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
||||
cwd: options.cwd,
|
||||
env: options.env
|
||||
});
|
||||
|
||||
if (!result.error && result.status === 0) {
|
||||
return { attempted: true, delivered: true, method: "taskkill", result };
|
||||
}
|
||||
|
||||
const combinedOutput = `${result.stderr}\n${result.stdout}`.trim();
|
||||
if (!result.error && looksLikeMissingProcessMessage(combinedOutput)) {
|
||||
return { attempted: true, delivered: false, method: "taskkill", result };
|
||||
}
|
||||
|
||||
if (result.error?.code === "ENOENT") {
|
||||
try {
|
||||
killImpl(pid);
|
||||
return { attempted: true, delivered: true, method: "kill" };
|
||||
} catch (error) {
|
||||
if (error?.code === "ESRCH") {
|
||||
return { attempted: true, delivered: false, method: "kill" };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
throw new Error(formatCommandFailure(result));
|
||||
}
|
||||
|
||||
try {
|
||||
killImpl(-pid, "SIGTERM");
|
||||
return { attempted: true, delivered: true, method: "process-group" };
|
||||
} catch (error) {
|
||||
if (error?.code !== "ESRCH") {
|
||||
try {
|
||||
killImpl(pid, "SIGTERM");
|
||||
return { attempted: true, delivered: true, method: "process" };
|
||||
} catch (innerError) {
|
||||
if (innerError?.code === "ESRCH") {
|
||||
return { attempted: true, delivered: false, method: "process" };
|
||||
}
|
||||
throw innerError;
|
||||
}
|
||||
}
|
||||
|
||||
return { attempted: true, delivered: false, method: "process-group" };
|
||||
}
|
||||
}
|
||||
|
||||
export function formatCommandFailure(result) {
|
||||
const parts = [`${result.command} ${result.args.join(" ")}`.trim()];
|
||||
if (result.signal) {
|
||||
parts.push(`signal=${result.signal}`);
|
||||
} else {
|
||||
parts.push(`exit=${result.status}`);
|
||||
}
|
||||
const stderr = (result.stderr || "").trim();
|
||||
const stdout = (result.stdout || "").trim();
|
||||
if (stderr) {
|
||||
parts.push(stderr);
|
||||
} else if (stdout) {
|
||||
parts.push(stdout);
|
||||
}
|
||||
return parts.join(": ");
|
||||
}
|
||||
Reference in New Issue
Block a user