feat(process-management): implemented native process tree management with platform-specific implementations
- Added native process tree killing and listing functions via new `ps` module in pi-natives crate with platform-specific implementations (Linux /proc, macOS libproc, Windows CreateToolhelp32Snapshot). - Refactored process termination logic to prioritize native process tree killing when available, improving reliability of process cleanup across platforms. - Removed detached process spawning across codebase (bash-executor, shell-session, kernel, lsp client, markitdown conversion) in favor of native process tree management. - Simplified ptree module by removing managed children tracking and postmortem cleanup hooks, delegating process lifecycle management to native implementations.
This commit is contained in:
Generated
+1
@@ -842,6 +842,7 @@ dependencies = [
|
||||
"html-to-markdown-rs",
|
||||
"ignore",
|
||||
"image",
|
||||
"libc",
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
|
||||
@@ -38,5 +38,8 @@ syntect = { version = "5.3", default-features = false, features = [
|
||||
html-to-markdown-rs = { version = "2.24", default-features = false }
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2"
|
||||
|
||||
@@ -26,4 +26,5 @@ pub mod highlight;
|
||||
pub mod html;
|
||||
pub mod image;
|
||||
pub mod keys;
|
||||
pub mod ps;
|
||||
pub mod text;
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
//! Cross-platform process tree management.
|
||||
//!
|
||||
//! Provides efficient process tree enumeration and termination without requiring
|
||||
//! processes to be spawned with `detached: true`.
|
||||
//!
|
||||
//! # Platform Implementation
|
||||
//! - **Linux**: Reads `/proc/{pid}/children` recursively
|
||||
//! - **macOS**: Uses `libproc` (`proc_listchildpids`)
|
||||
//! - **Windows**: Uses `CreateToolhelp32Snapshot` to build parent-child relationships
|
||||
//!
|
||||
//! # Example
|
||||
//! ```ignore
|
||||
//! use pi_natives::ps::kill_tree;
|
||||
//!
|
||||
//! // Kill process 1234 and all its descendants
|
||||
//! let killed = kill_tree(1234, 9); // SIGKILL
|
||||
//! ```
|
||||
|
||||
use napi_derive::napi;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod platform {
|
||||
use std::fs;
|
||||
|
||||
/// Recursively collect all descendant PIDs by reading /proc/{pid}/children.
|
||||
pub fn collect_descendants(pid: i32, pids: &mut Vec<i32>) {
|
||||
let children_path = format!("/proc/{pid}/task/{pid}/children");
|
||||
let Ok(content) = fs::read_to_string(&children_path) else {
|
||||
return;
|
||||
};
|
||||
|
||||
for part in content.split_whitespace() {
|
||||
if let Ok(child_pid) = part.parse::<i32>() {
|
||||
pids.push(child_pid);
|
||||
collect_descendants(child_pid, pids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Kill a process with the given signal.
|
||||
pub fn kill_pid(pid: i32, signal: i32) -> bool {
|
||||
// SAFETY: libc::kill is safe to call with any pid/signal combination
|
||||
unsafe { libc::kill(pid, signal) == 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod platform {
|
||||
use std::ptr;
|
||||
|
||||
#[link(name = "proc", kind = "dylib")]
|
||||
extern "C" {
|
||||
fn proc_listchildpids(ppid: i32, buffer: *mut i32, buffersize: i32) -> i32;
|
||||
}
|
||||
|
||||
/// Recursively collect all descendant PIDs using libproc.
|
||||
pub fn collect_descendants(pid: i32, pids: &mut Vec<i32>) {
|
||||
// First call to get count
|
||||
let count = unsafe { proc_listchildpids(pid, ptr::null_mut(), 0) };
|
||||
if count <= 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut buffer = vec![0i32; count as usize];
|
||||
let actual = unsafe {
|
||||
proc_listchildpids(
|
||||
pid,
|
||||
buffer.as_mut_ptr(),
|
||||
(buffer.len() * size_of::<i32>()) as i32,
|
||||
)
|
||||
};
|
||||
|
||||
if actual <= 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let child_count = actual as usize / size_of::<i32>();
|
||||
for &child_pid in &buffer[..child_count] {
|
||||
if child_pid > 0 {
|
||||
pids.push(child_pid);
|
||||
collect_descendants(child_pid, pids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Kill a process with the given signal.
|
||||
pub fn kill_pid(pid: i32, signal: i32) -> bool {
|
||||
// SAFETY: libc::kill is safe to call with any pid/signal combination
|
||||
unsafe { libc::kill(pid, signal) == 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod platform {
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
struct PROCESSENTRY32W {
|
||||
dwSize: u32,
|
||||
cntUsage: u32,
|
||||
th32ProcessID: u32,
|
||||
th32DefaultHeapID: usize,
|
||||
th32ModuleID: u32,
|
||||
cntThreads: u32,
|
||||
th32ParentProcessID: u32,
|
||||
pcPriClassBase: i32,
|
||||
dwFlags: u32,
|
||||
szExeFile: [u16; 260],
|
||||
}
|
||||
|
||||
type HANDLE = *mut std::ffi::c_void;
|
||||
const INVALID_HANDLE_VALUE: HANDLE = -1isize as HANDLE;
|
||||
const TH32CS_SNAPPROCESS: u32 = 0x00000002;
|
||||
const PROCESS_TERMINATE: u32 = 0x0001;
|
||||
|
||||
#[link(name = "kernel32")]
|
||||
extern "system" {
|
||||
fn CreateToolhelp32Snapshot(dwFlags: u32, th32ProcessID: u32) -> HANDLE;
|
||||
fn Process32FirstW(hSnapshot: HANDLE, lppe: *mut PROCESSENTRY32W) -> i32;
|
||||
fn Process32NextW(hSnapshot: HANDLE, lppe: *mut PROCESSENTRY32W) -> i32;
|
||||
fn CloseHandle(hObject: HANDLE) -> i32;
|
||||
fn OpenProcess(dwDesiredAccess: u32, bInheritHandle: i32, dwProcessId: u32) -> HANDLE;
|
||||
fn TerminateProcess(hProcess: HANDLE, uExitCode: u32) -> i32;
|
||||
}
|
||||
|
||||
/// Build a map of parent_pid -> [child_pids] for all processes.
|
||||
fn build_process_tree() -> HashMap<u32, Vec<u32>> {
|
||||
let mut tree: HashMap<u32, Vec<u32>> = HashMap::new();
|
||||
|
||||
unsafe {
|
||||
let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if snapshot == INVALID_HANDLE_VALUE {
|
||||
return tree;
|
||||
}
|
||||
|
||||
let mut entry: PROCESSENTRY32W = mem::zeroed();
|
||||
entry.dwSize = mem::size_of::<PROCESSENTRY32W>() as u32;
|
||||
|
||||
if Process32FirstW(snapshot, &mut entry) != 0 {
|
||||
loop {
|
||||
tree.entry(entry.th32ParentProcessID)
|
||||
.or_default()
|
||||
.push(entry.th32ProcessID);
|
||||
|
||||
if Process32NextW(snapshot, &mut entry) == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
}
|
||||
|
||||
tree
|
||||
}
|
||||
|
||||
/// Recursively collect all descendant PIDs.
|
||||
pub fn collect_descendants(pid: i32, pids: &mut Vec<i32>) {
|
||||
let tree = build_process_tree();
|
||||
collect_descendants_from_tree(pid as u32, &tree, pids);
|
||||
}
|
||||
|
||||
fn collect_descendants_from_tree(pid: u32, tree: &HashMap<u32, Vec<u32>>, pids: &mut Vec<i32>) {
|
||||
if let Some(children) = tree.get(&pid) {
|
||||
for &child_pid in children {
|
||||
pids.push(child_pid as i32);
|
||||
collect_descendants_from_tree(child_pid, tree, pids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Kill a process (signal is ignored on Windows, always terminates).
|
||||
pub fn kill_pid(pid: i32, _signal: i32) -> bool {
|
||||
unsafe {
|
||||
let handle = OpenProcess(PROCESS_TERMINATE, 0, pid as u32);
|
||||
if handle.is_null() || handle == INVALID_HANDLE_VALUE {
|
||||
return false;
|
||||
}
|
||||
let result = TerminateProcess(handle, 1);
|
||||
CloseHandle(handle);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Kill a process tree (the process and all its descendants).
|
||||
///
|
||||
/// Kills children first (bottom-up) to prevent orphan re-parenting issues.
|
||||
/// Returns the number of processes successfully killed.
|
||||
#[napi]
|
||||
pub fn kill_tree(pid: i32, signal: i32) -> u32 {
|
||||
let mut descendants = Vec::new();
|
||||
platform::collect_descendants(pid, &mut descendants);
|
||||
|
||||
let mut killed = 0u32;
|
||||
|
||||
// Kill children first (deepest first by reversing the DFS order)
|
||||
for &child_pid in descendants.iter().rev() {
|
||||
if platform::kill_pid(child_pid, signal) {
|
||||
killed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Kill the root process last
|
||||
if platform::kill_pid(pid, signal) {
|
||||
killed += 1;
|
||||
}
|
||||
|
||||
killed
|
||||
}
|
||||
|
||||
/// List all descendant PIDs of a process.
|
||||
///
|
||||
/// Returns an empty array if the process has no children or doesn't exist.
|
||||
#[napi]
|
||||
pub fn list_descendants(pid: i32) -> Vec<i32> {
|
||||
let mut descendants = Vec::new();
|
||||
platform::collect_descendants(pid, &mut descendants);
|
||||
descendants
|
||||
}
|
||||
@@ -476,7 +476,6 @@ describe("Context overflow error handling", () => {
|
||||
|
||||
// Start ollama server
|
||||
ollamaProcess = spawn("ollama", ["serve"], {
|
||||
detached: false,
|
||||
stdio: "ignore",
|
||||
});
|
||||
|
||||
|
||||
@@ -1379,7 +1379,6 @@ describe("Generate E2E Tests", () => {
|
||||
|
||||
// Start ollama server
|
||||
ollamaProcess = spawn("ollama", ["serve"], {
|
||||
detached: false,
|
||||
stdio: "ignore",
|
||||
});
|
||||
|
||||
|
||||
@@ -105,7 +105,6 @@ async function executeBashOnce(
|
||||
env: finalEnv,
|
||||
signal: options?.signal,
|
||||
timeout: options?.timeout,
|
||||
detached: true,
|
||||
});
|
||||
|
||||
// Pump streams - errors during abort/timeout are expected
|
||||
|
||||
@@ -254,7 +254,6 @@ class ShellSession {
|
||||
this.#child = ptree.spawn([this.config.shell, ...args], {
|
||||
stdin: "pipe",
|
||||
env: this.config.env,
|
||||
detached: !IS_WINDOWS,
|
||||
});
|
||||
|
||||
if (this.#child.proc.exitCode !== null) {
|
||||
|
||||
@@ -265,8 +265,8 @@ async function startGatewayProcess(
|
||||
stdin: "ignore",
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
detached: true,
|
||||
windowsHide: true,
|
||||
detached: true,
|
||||
env: kernelEnv,
|
||||
},
|
||||
);
|
||||
@@ -296,13 +296,13 @@ async function startGatewayProcess(
|
||||
await Bun.sleep(100);
|
||||
}
|
||||
|
||||
await procmgr.terminate({ target: gatewayProcess, group: true });
|
||||
gatewayProcess.kill();
|
||||
throw new Error("Gateway startup timeout");
|
||||
}
|
||||
|
||||
async function killGateway(pid: number, context: string): Promise<void> {
|
||||
try {
|
||||
await procmgr.terminate({ target: pid, group: true });
|
||||
await procmgr.terminate({ target: pid });
|
||||
} catch (err) {
|
||||
logger.warn("Failed to kill shared gateway process", {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
|
||||
@@ -490,7 +490,6 @@ export class PythonKernel {
|
||||
{
|
||||
cwd: options.cwd,
|
||||
env: kernelEnv,
|
||||
detached: true,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -411,7 +411,6 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
|
||||
|
||||
const proc = ptree.spawn([command, ...args], {
|
||||
cwd,
|
||||
detached: true,
|
||||
stdin: "pipe",
|
||||
env: env ? { ...process.env, ...env } : undefined,
|
||||
});
|
||||
|
||||
@@ -497,7 +497,6 @@ async function convertWithMarkitdown(
|
||||
allowNonZero: true,
|
||||
allowAbort: true,
|
||||
stderr: "buffer",
|
||||
detached: true,
|
||||
});
|
||||
|
||||
if (result.exitError?.aborted) {
|
||||
|
||||
@@ -54,7 +54,6 @@ export async function convertWithMarkitdown(
|
||||
signal,
|
||||
allowNonZero: true,
|
||||
stderr: "full",
|
||||
detached: true,
|
||||
});
|
||||
if (!result.ok) {
|
||||
return {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Exported `killTree` function to kill a process and all its descendants using platform-native APIs
|
||||
- Exported `listDescendants` function to list all descendant PIDs of a process
|
||||
- Added `dev:native` npm script to build debug native binaries with `--dev` flag
|
||||
- Added `OMP_DEV` environment variable support for loading and debugging development native builds
|
||||
- Exported keyboard parsing and matching functions: `parseKey`, `parseKittySequence`, `matchesLegacySequence`, and `matchesKey` for terminal input handling
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
*/
|
||||
|
||||
import * as path from "node:path";
|
||||
import { setNativeKillTree } from "@oh-my-pi/pi-utils";
|
||||
import type { FindMatch, FindOptions, FindResult } from "./find/types";
|
||||
import { native } from "./native";
|
||||
|
||||
setNativeKillTree(native.killTree);
|
||||
|
||||
// =============================================================================
|
||||
// Grep (ripgrep-based regex search)
|
||||
// =============================================================================
|
||||
@@ -115,3 +118,33 @@ export {
|
||||
type HtmlToMarkdownOptions,
|
||||
htmlToMarkdown,
|
||||
} from "./html/index";
|
||||
|
||||
// =============================================================================
|
||||
// Process management
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Kill a process and all its descendants.
|
||||
*
|
||||
* Uses platform-native APIs for efficiency:
|
||||
* - Linux: /proc/{pid}/children
|
||||
* - macOS: libproc (proc_listchildpids)
|
||||
* - Windows: CreateToolhelp32Snapshot
|
||||
*
|
||||
* @param pid - Process ID to kill
|
||||
* @param signal - Signal number (e.g., 9 for SIGKILL). Ignored on Windows.
|
||||
* @returns Number of processes successfully killed
|
||||
*/
|
||||
export function killTree(pid: number, signal: number): number {
|
||||
return native.killTree(pid, signal);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all descendant PIDs of a process.
|
||||
*
|
||||
* @param pid - Process ID to query
|
||||
* @returns Array of descendant PIDs (children, grandchildren, etc.)
|
||||
*/
|
||||
export function listDescendants(pid: number): number[] {
|
||||
return native.listDescendants(pid);
|
||||
}
|
||||
|
||||
@@ -90,6 +90,8 @@ export interface NativeBindings {
|
||||
matchesLegacySequence(data: string, keyName: string): boolean;
|
||||
parseKittySequence(data: string): ParsedKittyResult | null;
|
||||
matchesKey(data: string, keyId: string, kittyProtocolActive: boolean): boolean;
|
||||
killTree(pid: number, signal: number): number;
|
||||
listDescendants(pid: number): number[];
|
||||
}
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
@@ -178,6 +180,8 @@ function validateNative(bindings: NativeBindings, source: string): void {
|
||||
checkFn("parseKittySequence");
|
||||
checkFn("matchesKey");
|
||||
checkFn("visibleWidth");
|
||||
checkFn("killTree");
|
||||
checkFn("listDescendants");
|
||||
|
||||
if (missing.length) {
|
||||
throw new Error(
|
||||
|
||||
@@ -5,6 +5,7 @@ export * from "./glob";
|
||||
export * as logger from "./logger";
|
||||
export * as postmortem from "./postmortem";
|
||||
export * as procmgr from "./procmgr";
|
||||
export { setNativeKillTree } from "./procmgr";
|
||||
export * as ptree from "./ptree";
|
||||
export { AbortError, ChildProcess, Exception, NonZeroExitError } from "./ptree";
|
||||
export * from "./stream";
|
||||
|
||||
@@ -184,13 +184,32 @@ export function getShellConfig(customShellPath?: string): ShellConfig {
|
||||
return cachedShellConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function signature for native process tree killing.
|
||||
* Returns the number of processes killed.
|
||||
*/
|
||||
export type KillTreeFn = (pid: number, signal: number) => number;
|
||||
|
||||
/**
|
||||
* Global native kill tree function, injected by pi-natives when loaded.
|
||||
* Falls back to platform-specific behavior if not set.
|
||||
*/
|
||||
export let nativeKillTree: KillTreeFn | undefined;
|
||||
|
||||
/**
|
||||
* Set the native kill tree function. Called by pi-natives on load.
|
||||
*/
|
||||
export function setNativeKillTree(fn: KillTreeFn): void {
|
||||
nativeKillTree = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for terminating a process and all its descendants.
|
||||
*/
|
||||
export interface TerminateOptions {
|
||||
/** The process to terminate */
|
||||
target: Subprocess | number;
|
||||
/** Whether to terminate the process group (Windows only) */
|
||||
/** Whether to terminate the process tree (all descendants) */
|
||||
group?: boolean;
|
||||
/** Timeout in milliseconds */
|
||||
timeout?: number;
|
||||
@@ -293,31 +312,22 @@ export async function terminate(options: TerminateOptions): Promise<boolean> {
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (group) {
|
||||
try {
|
||||
if (IS_WINDOWS) {
|
||||
const taskkill = Bun.spawn({
|
||||
cmd: ["taskkill", "/F", "/T", "/PID", pid.toString()],
|
||||
stdin: "ignore",
|
||||
stdout: "ignore",
|
||||
stderr: "ignore",
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
});
|
||||
void taskkill.exited.catch(() => {});
|
||||
taskkill.unref();
|
||||
} else {
|
||||
if (nativeKillTree) {
|
||||
nativeKillTree(pid, 9);
|
||||
} else {
|
||||
if (group && !IS_WINDOWS) {
|
||||
try {
|
||||
process.kill(-pid, "SIGKILL");
|
||||
} catch {}
|
||||
}
|
||||
try {
|
||||
if (typeof target === "number") {
|
||||
process.kill(target, "SIGKILL");
|
||||
} else {
|
||||
target.kill("SIGKILL");
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
try {
|
||||
if (typeof target === "number") {
|
||||
process.kill(target, "SIGKILL");
|
||||
} else {
|
||||
target.kill("SIGKILL");
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return await Promise.race([Bun.sleep(timeout).then(() => false), exitPromise]);
|
||||
} finally {
|
||||
|
||||
@@ -10,11 +10,8 @@
|
||||
*/
|
||||
|
||||
import type { Spawn, Subprocess } from "bun";
|
||||
import { postmortem } from ".";
|
||||
import { terminate } from "./procmgr";
|
||||
|
||||
const managedChildren = new Set<ChildProcess>();
|
||||
|
||||
/** A Bun subprocess with stdout/stderr always piped (stdin may vary). */
|
||||
type PipedSubprocess<In extends InMask = InMask> = Subprocess<In, "pipe", "pipe">;
|
||||
|
||||
@@ -98,15 +95,9 @@ async function pump(
|
||||
* - Unix: negative PID signals the process group
|
||||
*/
|
||||
async function killChild(child: ChildProcess) {
|
||||
await terminate({ target: child.proc, group: child.isProcessGroup });
|
||||
await terminate({ target: child.proc });
|
||||
}
|
||||
|
||||
postmortem.register("managed-children", async () => {
|
||||
const children = Array.from(managedChildren);
|
||||
managedChildren.clear();
|
||||
await Promise.all(children.map(killChild));
|
||||
});
|
||||
|
||||
/**
|
||||
* Options for waiting for process exit and capturing output.
|
||||
*/
|
||||
@@ -205,10 +196,7 @@ export class ChildProcess<In extends InMask = InMask> {
|
||||
#stderrDone: Promise<void>;
|
||||
#exited: Promise<number>;
|
||||
|
||||
constructor(
|
||||
public readonly proc: PipedSubprocess<In>,
|
||||
public readonly isProcessGroup: boolean,
|
||||
) {
|
||||
constructor(public readonly proc: PipedSubprocess<In>) {
|
||||
const { promise: stderrDone, resolve: resolveStderrDone } = Promise.withResolvers<void>();
|
||||
this.#stderrDone = stderrDone;
|
||||
|
||||
@@ -245,8 +233,6 @@ export class ChildProcess<In extends InMask = InMask> {
|
||||
const { promise, resolve, reject } = Promise.withResolvers<number>();
|
||||
this.#exited = promise;
|
||||
|
||||
if (this.proc.exitCode === null) managedChildren.add(this);
|
||||
|
||||
// Normalize Bun's exited promise into our "exitReason / exitedCleanly" model.
|
||||
proc.exited
|
||||
.catch(() => null)
|
||||
@@ -279,9 +265,6 @@ export class ChildProcess<In extends InMask = InMask> {
|
||||
|
||||
this.#exitReason = ex;
|
||||
reject(ex);
|
||||
})
|
||||
.finally(() => {
|
||||
managedChildren.delete(this);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -426,7 +409,7 @@ export class ChildProcess<In extends InMask = InMask> {
|
||||
*/
|
||||
type ChildSpawnOptions<In extends InMask = InMask> = Omit<
|
||||
Spawn.SpawnOptions<In, "pipe", "pipe">,
|
||||
"stdout" | "stderr"
|
||||
"stdout" | "stderr" | "detached"
|
||||
> & {
|
||||
/** AbortSignal to cancel the process */
|
||||
signal?: AbortSignal;
|
||||
@@ -441,16 +424,15 @@ type ChildSpawnOptions<In extends InMask = InMask> = Omit<
|
||||
* @returns A ChildProcess instance.
|
||||
*/
|
||||
export function spawn<In extends InMask = InMask>(cmd: string[], options?: ChildSpawnOptions<In>): ChildProcess<In> {
|
||||
const { detached = false, timeout = -1, signal, ...rest } = options ?? {};
|
||||
const { timeout = -1, signal, ...rest } = options ?? {};
|
||||
const child = Bun.spawn(cmd, {
|
||||
stdin: "ignore",
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
detached,
|
||||
windowsHide: true,
|
||||
...rest,
|
||||
});
|
||||
const cproc = new ChildProcess(child, detached);
|
||||
const cproc = new ChildProcess(child);
|
||||
if (signal) cproc.attachSignal(signal);
|
||||
if (timeout > 0) cproc.attachTimeout(timeout);
|
||||
return cproc;
|
||||
|
||||
Reference in New Issue
Block a user