es-node-runner
Version:
Node runner that transpiles typescript or es modules using blazing fast ⚡ esbuild and restarts the process automatically on change. Suitable for node development server.
123 lines (122 loc) • 3.79 kB
JavaScript
import {exec, execSync} from 'child_process';
import {spawn} from 'cross-spawn';
import DEBUG from 'debug';
import {logger} from './utils/index.js';
const debug = DEBUG('es-node-runner:runner');
let child, reRun;
const isWin = process.platform === 'win32';
function run(spawnArgs) {
debug('spawning new sub process');
const subProcess = spawn('node', spawnArgs, {
stdio: [process.stdin, process.stdout, process.stderr],
env: {
...process.env,
},
});
reRun = run.bind(this, spawnArgs);
if (subProcess.signalCode) {
debug(`received '${subProcess.signalCode}' signal in sub process`);
logger.error(
'The command execution failed because the process exited too early.'
);
if (subProcess.signalCode === 'SIGKILL') {
logger.error(
'This probably means the system ran out of memory or someone called "kill -9" on the process.\n'
);
} else if (subProcess.signalCode === 'SIGTERM') {
logger.error(
'Someone might have called `kill` or `killall`, or the system could be shutting down.\n'
);
}
debug('exiting sub process with code 1');
process.exit(1);
}
subProcess.on('spawn', () => {
debug(
child?.pid !== subProcess.pid
? 'sub process spawned'
: 'sub process respawned'
);
child = subProcess;
});
subProcess.on('error', (error) => {
debug(`error occurred in sub process ${subProcess.pid}`);
logger.error(`[Error]: ${error.code} - ${error.message}\n`);
if (error.code === 'ENOENT') {
debug('exiting sub process with code 1');
process.exit(1);
} else {
throw error;
}
});
subProcess.once('exit', (code) => {
debug(`sub process ${subProcess.pid} exited with code ${code}`);
child = null;
if (code > 0) {
logger.error(`Sub process exited with code ${code}\n`);
process.exit(0);
}
});
}
function stop() {
let status = -1;
if (child && child.pid) {
debug('terminating child processes');
if (isWin) {
debug('executing windows kill cmd');
try {
const terminationStatus = execSync(
`powershell "(Invoke-CimMethod -InputObject (Get-CimInstance Win32_Process | ` +
`Where-Object { $_.ParentProcessId -eq ${child.pid} }) -MethodName Terminate).ReturnValue"`
);
status = terminationStatus.readUInt8(0);
debug('child processes killed gracefully');
} catch (error) {
debug('terminating child processes forcefully');
try {
exec(`TASKKILL /PID ${child.pid} /F /T`);
status = 48;
} catch (error) {
logger.error(
'Could not terminate cleanly. One or more child processes were still running. ' +
`More info : ${error}\n`
);
process.exit(1);
}
}
} else {
debug('executing non-windows kill cmd');
try {
const buffer = execSync(
`ps --ppid ${child.pid} -o pid | grep -oP '[0-9]+' | xargs -r`
);
const pids = buffer.toString().match(/[0-9]+/g);
if (Array.isArray(pids)) {
pids.forEach((pid) => exec(`kill -TERM ${pid}`));
}
child.kill();
status = 48;
debug('child processes killed gracefully');
} catch (error) {
logger.error(
'Could not terminate cleanly. One or more child processes were still running. ' +
`More info : ${error}\n`
);
process.exit(1);
}
}
} else {
status = 48;
}
return status;
}
function restart() {
debug('restarting process after rebuild');
if (stop() === 48) {
reRun();
} else {
debug('could not restart process');
logger.error('Could not restart process\n');
}
}
export {run, restart};