116 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			116 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| const Yargs = require('yargs');
 | |
| const {spawn} = require("child_process");
 | |
| 
 | |
| const yargs = Yargs(process.argv.splice(2))
 | |
|     .scriptName('kr')
 | |
|     .usage('kr [args] <process-to-run>')
 | |
|     .option('_rpm', {
 | |
|         default: 0,
 | |
|         description: 'The amount of Retries Per Minute, before we stop retrying',
 | |
|         type: 'number'
 | |
|     })
 | |
|     .option('_rph', {
 | |
|         default: 0,
 | |
|         description: 'The amount of Retries Per Hour, before we stop retrying',
 | |
|         type: 'number'
 | |
|     })
 | |
|     .option('_delay', {
 | |
|         default: 0,
 | |
|         description: 'Time in seconds we want to delay the restart with.',
 | |
|         type: 'number'
 | |
|     })
 | |
|     .conflicts('_rpm', '_rph')
 | |
|     .help();
 | |
| 
 | |
| const { _ = [], _rpm: rpm, _rph: rph, _delay: delay } = yargs.argv;
 | |
| const [command, ...args  ] = _;
 | |
| 
 | |
| if (!command) {
 | |
|     return yargs.showHelp();
 | |
| }
 | |
| 
 | |
| const SECONDS_IN_A_MINUTE = 60;
 | |
| const SECONDS_IN_A_HOUR = 60 * 60;
 | |
| 
 | |
| let historyMax = 4;
 | |
| let seconds = SECONDS_IN_A_MINUTE;
 | |
| let restartName = 'minute';
 | |
| 
 | |
| if (rpm) {
 | |
|     historyMax = rpm
 | |
| } else if (rph) {
 | |
|     historyMax = rph
 | |
|     seconds = SECONDS_IN_A_HOUR
 | |
|     restartName = 'hour'
 | |
| }
 | |
| 
 | |
| /** @type {Object<number, string>} */
 | |
| const history = {};
 | |
| 
 | |
| const getNow = () => Math.ceil(Date.now() / 1000); // Time in seconds
 | |
| 
 | |
| /** @param {string} logs */
 | |
| const pushHistory = (logs) => history[getNow() + seconds] = logs;
 | |
| 
 | |
| const updateHistory = () => {
 | |
|     const now = getNow();
 | |
|     const clearKeys = Object.keys(history)
 | |
|         .filter((time) => time <= now);
 | |
| 
 | |
|     clearKeys.forEach((key) => delete history[key]);
 | |
| }
 | |
| 
 | |
| const checkHistory = () => Object.keys(history).length <= historyMax;
 | |
| 
 | |
| const restart = () => setTimeout(() => runCommand(delay), delay * 1000);
 | |
| 
 | |
| const runCommand = () => {
 | |
|     const runner = spawn(command, args);
 | |
| 
 | |
|     const commandLogs = [];
 | |
| 
 | |
|     const pushLog = (logEntry) => commandLogs.push(logEntry);
 | |
| 
 | |
|     const trimLog = () => {
 | |
|         if(commandLogs.length > 5000) commandLogs.shift()
 | |
|     };
 | |
| 
 | |
|     const clearLogs = () => commandLogs.length = 0;
 | |
| 
 | |
|     const getLogs = () => commandLogs.join('\n');
 | |
| 
 | |
|     const handleOut = (type, message) => {
 | |
|         pushLog(`[${type}]\t${message}`);
 | |
|         trimLog();
 | |
|     };
 | |
| 
 | |
|     const handleExit = (exitCode) => {
 | |
|         if (exitCode === 0) {
 | |
|             console.log(`Exit code: ${exitCode}`);
 | |
|         } else{
 | |
|             console.log(`[CRASH] exit code: ${exitCode}`)
 | |
|             pushHistory(getLogs());
 | |
|             clearLogs();
 | |
|             updateHistory();
 | |
|             if (checkHistory()) {
 | |
|                 console.log('Restarting...');
 | |
|                 restart();
 | |
|             } else {
 | |
|                 console.error(`The process crashed more then ${historyMax} times in the past ${restartName}, stop retrying.`);
 | |
|                 console.error(`See below the last crash log:`);
 | |
|                 const keys = Object.keys(history);
 | |
|                 const lastKey = keys[keys.length-1];
 | |
|                 console.log(history[lastKey]);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     runner.on('close', (exitCode) => handleExit(exitCode))
 | |
|     runner.stdout.on('data', (data) => console.log(data.toString().trim()))
 | |
|     runner.stdout.on('data', (data) => handleOut('LOG', data.toString().trim()))
 | |
|     runner.stderr.on('data', (data) => handleOut('ERR', data.toString().trim()))
 | |
| }
 | |
| 
 | |
| // Leggo!~
 | |
| runCommand();
 |