import * as plugins from './smartexit.plugins.js'; import type { TProcessSignal } from './smartexit.types.js'; export interface IProcessTreeSignalResult { status: 'signalled' | 'notFound' | 'failed'; error?: unknown; } export interface IProcessGroupProbeResult { status: 'exists' | 'notFound' | 'failed'; error?: unknown; } const errorCodeFor = (errorArg: unknown): string | undefined => { if (typeof errorArg !== 'object' || errorArg === null || !('code' in errorArg)) { return undefined; } const { code } = errorArg; return typeof code === 'string' ? code : undefined; }; const probeExactProcessByPid = (pidArg: number): IProcessGroupProbeResult => { try { process.kill(pidArg, 0); return { status: 'exists' }; } catch (error) { const errorCode = errorCodeFor(error); if (errorCode === 'ESRCH') { return { status: 'notFound' }; } if (errorCode === 'EPERM') { return { status: 'exists' }; } return { status: 'failed', error }; } }; /** * Signal one owned process tree synchronously so shutdown and the final exit * safety net use the same OS contract. */ export const signalProcessTreeByPid = ( pidArg: number, signalArg: TProcessSignal, ): IProcessTreeSignalResult => { if (process.platform === 'win32') { if (probeExactProcessByPid(pidArg).status === 'notFound') { return { status: 'notFound' }; } try { plugins.childProcess.execFileSync( 'taskkill', ['/T', '/F', '/PID', String(pidArg)], { stdio: 'ignore' }, ); return { status: 'signalled' }; } catch (error) { if (probeExactProcessByPid(pidArg).status === 'notFound') { return { status: 'notFound' }; } return { status: 'failed', error }; } } try { process.kill(-pidArg, signalArg); return { status: 'signalled' }; } catch (error) { if (errorCodeFor(error) === 'ESRCH') { return { status: 'notFound' }; } return { status: 'failed', error }; } }; /** Probe the exact POSIX process group owned by a detached group leader. */ export const probeProcessGroupByPid = (pidArg: number): IProcessGroupProbeResult => { try { process.kill(-pidArg, 0); return { status: 'exists' }; } catch (error) { const errorCode = errorCodeFor(error); if (errorCode === 'ESRCH') { return { status: 'notFound' }; } if (errorCode === 'EPERM') { return { status: 'exists' }; } return { status: 'failed', error }; } };