| 'use strict'; | 
| const os = require('os'); | 
| const onExit = require('signal-exit'); | 
| const pFinally = require('p-finally'); | 
|   | 
| const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5; | 
|   | 
| // Monkey-patches `childProcess.kill()` to add `forceKillAfterTimeout` behavior | 
| const spawnedKill = (kill, signal = 'SIGTERM', options = {}) => { | 
|     const killResult = kill(signal); | 
|     setKillTimeout(kill, signal, options, killResult); | 
|     return killResult; | 
| }; | 
|   | 
| const setKillTimeout = (kill, signal, options, killResult) => { | 
|     if (!shouldForceKill(signal, options, killResult)) { | 
|         return; | 
|     } | 
|   | 
|     const timeout = getForceKillAfterTimeout(options); | 
|     setTimeout(() => { | 
|         kill('SIGKILL'); | 
|     }, timeout).unref(); | 
| }; | 
|   | 
| const shouldForceKill = (signal, {forceKillAfterTimeout}, killResult) => { | 
|     return isSigterm(signal) && forceKillAfterTimeout !== false && killResult; | 
| }; | 
|   | 
| const isSigterm = signal => { | 
|     return signal === os.constants.signals.SIGTERM || | 
|         (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM'); | 
| }; | 
|   | 
| const getForceKillAfterTimeout = ({forceKillAfterTimeout = true}) => { | 
|     if (forceKillAfterTimeout === true) { | 
|         return DEFAULT_FORCE_KILL_TIMEOUT; | 
|     } | 
|   | 
|     if (!Number.isInteger(forceKillAfterTimeout) || forceKillAfterTimeout < 0) { | 
|         throw new TypeError(`Expected the \`forceKillAfterTimeout\` option to be a non-negative integer, got \`${forceKillAfterTimeout}\` (${typeof forceKillAfterTimeout})`); | 
|     } | 
|   | 
|     return forceKillAfterTimeout; | 
| }; | 
|   | 
| // `childProcess.cancel()` | 
| const spawnedCancel = (spawned, context) => { | 
|     const killResult = spawned.kill(); | 
|   | 
|     if (killResult) { | 
|         context.isCanceled = true; | 
|     } | 
| }; | 
|   | 
| const timeoutKill = (spawned, signal, reject) => { | 
|     spawned.kill(signal); | 
|     reject(Object.assign(new Error('Timed out'), {timedOut: true, signal})); | 
| }; | 
|   | 
| // `timeout` option handling | 
| const setupTimeout = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise) => { | 
|     if (timeout === 0 || timeout === undefined) { | 
|         return spawnedPromise; | 
|     } | 
|   | 
|     if (!Number.isInteger(timeout) || timeout < 0) { | 
|         throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`); | 
|     } | 
|   | 
|     let timeoutId; | 
|     const timeoutPromise = new Promise((resolve, reject) => { | 
|         timeoutId = setTimeout(() => { | 
|             timeoutKill(spawned, killSignal, reject); | 
|         }, timeout); | 
|     }); | 
|   | 
|     const safeSpawnedPromise = pFinally(spawnedPromise, () => { | 
|         clearTimeout(timeoutId); | 
|     }); | 
|   | 
|     return Promise.race([timeoutPromise, safeSpawnedPromise]); | 
| }; | 
|   | 
| // `cleanup` option handling | 
| const setExitHandler = (spawned, {cleanup, detached}, timedPromise) => { | 
|     if (!cleanup || detached) { | 
|         return timedPromise; | 
|     } | 
|   | 
|     const removeExitHandler = onExit(() => { | 
|         spawned.kill(); | 
|     }); | 
|   | 
|     // TODO: Use native "finally" syntax when targeting Node.js 10 | 
|     return pFinally(timedPromise, removeExitHandler); | 
| }; | 
|   | 
| module.exports = { | 
|     spawnedKill, | 
|     spawnedCancel, | 
|     setupTimeout, | 
|     setExitHandler | 
| }; |