Created Keep Running (kr) command

This commit is contained in:
Ian Wijma 2023-11-19 13:28:59 +11:00
commit 510d299276
6 changed files with 387 additions and 0 deletions

18
.github/workflows/build.yaml vendored Normal file
View File

@ -0,0 +1,18 @@
name: Bonsai CI
run-name: ${{ gitea.actor }} is running spotify.local CI pipeline 🚀
on: [release]
concurrency: 'true'
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Download dependencies
run: npm ci
- name: Build binairy files
run: npm run build
- uses: actions/upload-artifact@v3
with:
path: bin

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea/
node_modules/
out/

11
bin/run.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
SLEEP=${1:-'10'}
echo "Sleeping for $SLEEP seconds...";
sleep $SLEEP;
echo "Waking up!";
exit 1;

213
package-lock.json generated Normal file
View File

@ -0,0 +1,213 @@
{
"name": "kr",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kr",
"version": "1.0.0",
"dependencies": {
"yargs": "^17.7.2"
},
"bin": {
"kr": "src/index.js"
},
"devDependencies": {
"@types/node": "^20.8.10",
"@types/yargs": "^17.0.29"
}
},
"node_modules/@types/node": {
"version": "20.9.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz",
"integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/yargs": {
"version": "17.0.31",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz",
"integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==",
"dev": true,
"dependencies": {
"@types/yargs-parser": "*"
}
},
"node_modules/@types/yargs-parser": {
"version": "21.0.3",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
"dev": true
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"engines": {
"node": ">=6"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"engines": {
"node": ">=12"
}
}
}
}

27
package.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "kr",
"version": "1.0.0",
"description": "",
"bin": "src/index.js",
"main": "src/index.js",
"scripts": {
"build": "pkg package.json"
},
"pkg": {
"scripts": "src/**/*.js",
"targets": [
"node18-linux-x64",
"node18-macos-x64",
"node18-win-x64"
],
"outputPath": "out"
},
"author": "Ian Wijma",
"dependencies": {
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/node": "^20.8.10",
"@types/yargs": "^17.0.29"
}
}

115
src/index.js Normal file
View File

@ -0,0 +1,115 @@
const Yargs = require('yargs');
const {spawn} = require("child_process");
const SECONDS_IN_A_MINUTE = 60;
const SECONDS_IN_A_HOUR = 60 * 60;
const yargs = Yargs(process.argv.splice(2))
.scriptName('kr')
.usage('kr [args] <thing-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'
})
.help();
const { _ = [], rpm, rph } = yargs.argv;
const [command, ...args ] = _;
if (!command) {
return yargs.showHelp();
}
let historyMax = 4;
let seconds = SECONDS_IN_A_MINUTE;
let restartName = 'minute';
if (rpm && rph) {
return console.error('Currently, can not define both --rpm and --rph, please choose only one.');
} else if (rpm) {
historyMax = rpm
} else if (rph) {
historyMax = rph
seconds = SECONDS_IN_A_HOUR
restartName = 'hour'
}
/**
* @type {Object<number, string>}
*/
const history = {};
/**
* @param {string} logs
*/
const pushHistory = (logs) => history[(new Date).getTime()] = logs;
const updateHistory = () => {
const clearKeys = [];
const maxAge = (new Date).getTime() + seconds;
for (const historyTime in history) {
if (historyTime > maxAge) {
clearKeys.push(historyTime)
}
}
clearKeys.forEach((key) => delete history[key]);
}
const checkHistory = () => Object.keys(history).length <= historyMax;
const runCommand = () => {
console.log(`Running ${command} ${args.join(' ')}`);
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) => {
console.log(`[CRASH] exit code: ${exitCode}`)
if (exitCode !== 0) {
pushHistory(getLogs());
clearLogs();
updateHistory();
if (checkHistory()) {
console.log('Restarting...');
runCommand();
} else {
console.error(`The process crashed more then ${historyMax} times in the past ${restartName}, stop retrying.`);
console.error(`See below the past ${historyMax} crashes:`);
for (const time in history) {
console.error(`Crash @ ${time}:\n${history[time]}`);
}
}
}
}
runner.on('close', (exitCode) => handleExit(exitCode))
runner.stdout.on('data', (data) => console.log(data.toString().trim()))
runner.stdout.on('data', (data) => handleOut('stdout', data.toString().trim()))
runner.stderr.on('data', (data) => handleOut('stderr', data.toString().trim()))
}
// Command goes BRRRR
runCommand();