const logger = require('./logger.js'); const { spawn } = require('child_process'); const { create } = require('domain'); const STATE_OK = 'ok'; const STATE_DETACHED = 'detached'; const STATE_REJECTED = 'rejected'; const STATE_KILLED = 'killed'; const STATE_ERROR = 'error'; const cmds = new Map(); let cmdId = -1; async function execute(endpoint) { if (endpoint === undefined) { return createResult(STATE_REJECTED, undefined, 'endpoint is not defined'); } let unique = endpoint.options?.unique?.toString(); if (unique !== undefined && isCommandActive(endpoint)) { unique = unique.toLowerCase(); switch (unique.toLowerCase()) { case 'true': logger.info('not executing unique command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\') because it is already active'); return createResult(STATE_REJECTED, undefined, 'unique command is already active'); case 'restart': logger.info('killing and restarting unique command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')...'); await killCommand(endpoint); break; case 'toggle': logger.info('stopping unique command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')...'); await killCommand(endpoint); return createResult(STATE_KILLED); } } return new Promise((resolve, reject) => { cmdId++; logger.info('executing command #' + cmdId + ' \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')...'); let cmd = spawn(endpoint.command, endpoint.args); cmd.id = cmdId; cmd.timestamp = new Date().getTime(); cmd.data = ''; cmd.error = ''; cmd.stdout.on('data', (data) => { cmd.data += data; }); cmd.stderr.on('data', (err) => { if (err.toString().toLowerCase().contains('warning')) { cmd.data += err; } cmd.error += err; }); cmd.on('spawn', () => { logger.info('spawned command #' + cmd.id + ' \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')...'); addCommand(cmd, endpoint); if (endpoint.options?.timeout && !isNaN(endpoint.options?.timeout)) { setTimeout(async () => { if (!cmds.has(endpoint)) { return; } logger.warn('killing timed out command #' + cmd.id + ' \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\') after ' + endpoint.options.timeout + 'ms...'); await killCommand(endpoint); }, endpoint.options.timeout); } if (endpoint.options?.detach) { return resolve(createResult(STATE_DETACHED, cmd.data, cmd.error)); } }); cmd.on('error', (err) => { cmd.error += err; removeCommand(endpoint); if (endpoint.options?.detach) { reject(createResult(STATE_ERROR, cmd.data, cmd.error)); } }); cmd.on('exit', (code) => { if (code === null) { code = 0; } removeCommand(endpoint); let fn = logger.info; let msg = 'command #' + cmd.id + ' \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\') finished with exit code ' + code + ' after ' + (new Date().getTime() - cmd.timestamp) + 'ms'; if (cmd.error !== undefined && cmd.error.length > 0) { cmd.error = error.trim(); msg += ' > error: ' + cmd.error; fn = logger.error; reject(createResult(STATE_ERROR, cmd.data, cmd.error)); } if (cmd.data !== undefined && cmd.data.length > 0) { cmd.data = cmd.data.trim(); msg += ' > data: ' + cmd.data; } fn(msg); resolve(createResult(STATE_OK, cmd.data, cmd.error)); }); }); } function createResult(state, data, error) { if (state === undefined ) { return; } return { state, data, error } } function addCommand(command, endpoint) { if (command === undefined || endpoint === undefined) { return; } cmds.set(endpoint, command); } function removeCommand(endpoint) { if (endpoint === undefined) { return; } cmds.delete(endpoint); } function isCommandActive(endpoint) { return endpoint !== undefined && cmds.has(endpoint); } async function killCommand(endpoint) { if (endpoint === undefined) { return; } const command = cmds.get(endpoint); if (command === undefined) { return; } process.kill(command.pid, 'SIGINT'); while (isCommandActive(endpoint)) { await sleep(1); } } async function sleep(milliseconds) { return new Promise((resolve, reject) => { setTimeout(resolve, milliseconds); }); } module.exports = { execute }