2024-11-03 09:57:44 +00:00
|
|
|
#! /usr/bin/env node
|
|
|
|
// @ts-check
|
|
|
|
|
|
|
|
const fs = require("fs");
|
|
|
|
const path = require("path");
|
|
|
|
const { spawn, ChildProcess, execSync } = require("child_process");
|
|
|
|
const readline = require("readline");
|
|
|
|
const colors = require("./console-colors");
|
|
|
|
|
|
|
|
////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////
|
|
|
|
|
2024-12-04 06:57:15 +00:00
|
|
|
setInterval(() => {
|
|
|
|
console.log(
|
|
|
|
`Batchrun Running for ${process.uptime().toLocaleString()}s ...`
|
|
|
|
);
|
|
|
|
}, 60000);
|
|
|
|
|
|
|
|
////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////
|
|
|
|
|
|
|
|
if (process.argv[process.argv.length - 1]?.match(/^--version$|^-v$/)) {
|
|
|
|
console.log(`Batchrun v${require("./package.json").version}`);
|
|
|
|
process.exit();
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////
|
|
|
|
|
2024-11-03 09:57:44 +00:00
|
|
|
/** @type {string[]} */
|
|
|
|
const processesStrings = [];
|
|
|
|
|
|
|
|
/** @type {ChildProcess[]} */
|
|
|
|
let processes = [];
|
|
|
|
|
2024-12-04 05:28:32 +00:00
|
|
|
const isWindows = process.platform.match(/win/i);
|
|
|
|
const isMac = process.platform.match(/darwin/i);
|
|
|
|
const isLinux = process.platform.match(/linux/i);
|
|
|
|
|
2024-11-03 09:57:44 +00:00
|
|
|
////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////
|
|
|
|
|
|
|
|
const rl = readline.createInterface({
|
|
|
|
input: process.stdin,
|
|
|
|
output: process.stdout,
|
|
|
|
});
|
|
|
|
|
|
|
|
rl.on("line", (input) => {
|
|
|
|
readCommands(input);
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Read Lines and execute commands
|
|
|
|
* @param {string} input
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
function readCommands(input) {
|
2024-12-04 06:57:15 +00:00
|
|
|
if (input?.match(/^(reload|restart|reboot|r)$/i)) {
|
2024-11-03 09:57:44 +00:00
|
|
|
console.log(
|
|
|
|
` - ${colors.FgBlue}Reloading processes ...${colors.Reset}`
|
|
|
|
);
|
|
|
|
restartAll();
|
2024-12-04 06:57:15 +00:00
|
|
|
} else if (input?.match(/^(reload|restart|reboot|r) \d/i)) {
|
2024-11-03 09:57:44 +00:00
|
|
|
const processedIndexesString = input.split(" ")[1];
|
|
|
|
const processedIndexes = processedIndexesString
|
|
|
|
? processedIndexesString.split(",")
|
|
|
|
: null;
|
|
|
|
|
|
|
|
if (!processedIndexes?.length) {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgRed}Error:${colors.Reset} No processes to reload`
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgBlue}Reloading processes ${processedIndexesString} ...${colors.Reset}`
|
|
|
|
);
|
|
|
|
processedIndexes.forEach((index) => {
|
|
|
|
restartOne(index);
|
|
|
|
});
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgGreen}Processes Restarted Successfully ${processedIndexesString} ...${colors.Reset}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (input?.match(/^kill$/i)) {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgYellow}Killing processes ...${colors.Reset}`
|
|
|
|
);
|
|
|
|
process.exit();
|
|
|
|
} else if (input?.match(/^kill \d/i)) {
|
|
|
|
const processedIndexesString = input.split(" ")[1];
|
|
|
|
const processedIndexes = processedIndexesString
|
|
|
|
? processedIndexesString.split(",")
|
|
|
|
: null;
|
|
|
|
|
|
|
|
if (!processedIndexes?.length) {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgRed}Error:${colors.Reset} No processes to reload`
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgYellow}Killing processes ${processedIndexesString} ...${colors.Reset}`
|
|
|
|
);
|
|
|
|
processedIndexes.forEach((index) => {
|
|
|
|
killOne(index);
|
|
|
|
});
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgGreen}Processes Killed ${processedIndexesString} ...${colors.Reset}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
process.stdin.on("keypress", (character, key) => {
|
|
|
|
if (key.ctrl && key.name === "r") {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgBlue}Reloading processes ...${colors.Reset}`
|
|
|
|
);
|
|
|
|
restartAll();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-12-04 06:57:15 +00:00
|
|
|
/**
|
|
|
|
* Cleanup function to kill all child processes
|
|
|
|
*/
|
|
|
|
function cleanupChildProcesses() {
|
|
|
|
for (const childProcess of processes) {
|
|
|
|
try {
|
|
|
|
if (childProcess.pid) {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgYellow}Killing process PID: ${childProcess.pid}${colors.Reset}`
|
|
|
|
);
|
|
|
|
killProcessForce(childProcess.pid);
|
|
|
|
}
|
|
|
|
childProcess.kill();
|
|
|
|
} catch (error) {
|
|
|
|
console.error(
|
|
|
|
` - ${colors.FgRed}Error:${colors.Reset} Failed to kill process PID: ${childProcess.pid}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
processes = [];
|
|
|
|
}
|
|
|
|
|
2024-11-03 09:57:44 +00:00
|
|
|
process.on("exit", (code) => {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgBlue}Process exited with code ${code}${colors.Reset}`
|
|
|
|
);
|
|
|
|
rl.close();
|
2024-12-04 06:57:15 +00:00
|
|
|
cleanupChildProcesses();
|
|
|
|
});
|
|
|
|
|
|
|
|
process.on("SIGINT", () => {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgYellow}SIGINT received, exiting...${colors.Reset}`
|
|
|
|
);
|
|
|
|
process.exit();
|
|
|
|
});
|
|
|
|
|
|
|
|
process.on("SIGTERM", () => {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgYellow}SIGTERM received, exiting...${colors.Reset}`
|
|
|
|
);
|
|
|
|
process.exit();
|
2024-11-03 09:57:44 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////
|
|
|
|
|
|
|
|
const argvProcessListIndex = process.argv.indexOf("batch-run") + 1;
|
|
|
|
const argvProcessList = process.argv.at(-1);
|
|
|
|
const processesFilePath = path.resolve(process.cwd(), "batchrun.config.json");
|
|
|
|
|
|
|
|
if (argvProcessList?.match(/batchrun/i) && !fs.existsSync(processesFilePath)) {
|
|
|
|
console.error(
|
|
|
|
` - ${colors.FgRed}Error:${colors.Reset} No arguments to run`
|
|
|
|
);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fs.existsSync(processesFilePath)) {
|
|
|
|
const processesFile = fs.readFileSync(processesFilePath, "utf8");
|
|
|
|
const processesArray = JSON.parse(processesFile);
|
|
|
|
for (let i = 0; i < processesArray.length; i++) {
|
|
|
|
const processString = processesArray[i];
|
|
|
|
const strippedProcessString = processString.trim();
|
|
|
|
processesStrings.push(strippedProcessString);
|
|
|
|
}
|
|
|
|
} else if (argvProcessList) {
|
|
|
|
const processesArray = argvProcessList.split(",");
|
|
|
|
for (let i = 0; i < processesArray.length; i++) {
|
|
|
|
const processString = processesArray[i];
|
|
|
|
const strippedProcessString = processString.trim();
|
|
|
|
processesStrings.push(strippedProcessString);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.error(
|
|
|
|
` - ${colors.FgRed}Error:${colors.Reset} No arguments to run or \`batchrun.config.json\` file present`
|
|
|
|
);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!processesStrings?.[0]) {
|
|
|
|
console.error(
|
|
|
|
` - ${colors.FgRed}Error:${colors.Reset} No processes to run`
|
|
|
|
);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @type {import("child_process").SpawnOptions} */
|
|
|
|
const spawnOptions = {
|
|
|
|
cwd: process.cwd(),
|
2024-12-04 05:28:32 +00:00
|
|
|
shell: isWindows ? "bash.exe" : undefined,
|
2024-12-04 06:57:15 +00:00
|
|
|
stdio: ["pipe", "inherit", "inherit"],
|
2024-11-03 09:57:44 +00:00
|
|
|
detached: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start all processes
|
|
|
|
*/
|
|
|
|
function startProcesses() {
|
|
|
|
for (let i = 0; i < processesStrings.length; i++) {
|
|
|
|
const processString = processesStrings[i];
|
|
|
|
const processStringArray = processString.split(" ");
|
|
|
|
const targetProcess = processStringArray.shift();
|
2024-12-04 05:28:32 +00:00
|
|
|
|
2024-11-03 09:57:44 +00:00
|
|
|
if (targetProcess) {
|
|
|
|
const childProcess = spawn(
|
|
|
|
targetProcess,
|
|
|
|
processStringArray,
|
|
|
|
spawnOptions
|
|
|
|
);
|
2024-12-04 05:28:32 +00:00
|
|
|
|
2024-11-03 09:57:44 +00:00
|
|
|
if (childProcess) {
|
2024-12-04 06:57:15 +00:00
|
|
|
childProcess.on("exit", (code, signal) => {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgRed}Process ${i} exited with code ${code} and signal ${signal}${colors.Reset}`
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
childProcess.on("error", (err) => {
|
|
|
|
console.error(
|
|
|
|
` - ${colors.FgYellow}Error:${colors.Reset} Failed to start process ${i}: ${err.message}`
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
childProcess.on("close", (code, signal) => {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgRed}Process ${i} closed with code ${code} and signal ${signal}${colors.Reset}`
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2024-11-03 09:57:44 +00:00
|
|
|
processes.push(childProcess);
|
|
|
|
} else {
|
|
|
|
console.error(
|
|
|
|
` - ${colors.FgRed}Error:${colors.Reset} Failed to start process ${i}`
|
|
|
|
);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.error(
|
|
|
|
` - ${colors.FgRed}Error:${colors.Reset} A target process is not defined in \`${processString}\``
|
|
|
|
);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restart All Processes
|
|
|
|
*/
|
|
|
|
function restartAll() {
|
|
|
|
for (let i = 0; i < processes.length; i++) {
|
|
|
|
const childProcess = processes[i];
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (childProcess.pid) killProcessForce(childProcess.pid);
|
|
|
|
childProcess.kill();
|
|
|
|
// processes.splice(i, 1);
|
|
|
|
} catch (error) {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgRed}Error:${colors.Reset} Failed to kill process ${childProcess.pid}`
|
|
|
|
);
|
|
|
|
process.exit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgGreen}Restarted ${processes.length} processes${colors.Reset}`
|
|
|
|
);
|
|
|
|
|
|
|
|
processes = [];
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
startProcesses();
|
|
|
|
}, 500);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restart a single process
|
|
|
|
* @param {string} index
|
|
|
|
*/
|
|
|
|
function restartOne(index) {
|
|
|
|
const childProcess = processes[parseInt(index)];
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (childProcess.pid) killProcessForce(childProcess.pid);
|
|
|
|
childProcess.kill();
|
|
|
|
} catch (error) {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgRed}Error:${colors.Reset} Failed to kill process ${childProcess.pid}`
|
|
|
|
);
|
|
|
|
process.exit();
|
|
|
|
}
|
|
|
|
|
|
|
|
const processString = processesStrings[index];
|
|
|
|
const processStringArray = processString.split(" ");
|
|
|
|
const targetProcess = processStringArray.shift();
|
|
|
|
const newChildProcess = spawn(
|
|
|
|
targetProcess,
|
|
|
|
processStringArray,
|
|
|
|
spawnOptions
|
|
|
|
);
|
|
|
|
processes.splice(parseInt(index), 1, newChildProcess);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Kill a single process
|
|
|
|
* @param {string} index
|
|
|
|
*/
|
|
|
|
function killOne(index) {
|
|
|
|
const childProcess = processes[parseInt(index)];
|
|
|
|
if (childProcess.pid) killProcessForce(childProcess.pid);
|
|
|
|
childProcess.kill();
|
|
|
|
|
|
|
|
try {
|
|
|
|
} catch (error) {
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgRed}Error:${colors.Reset} Failed to kill process ${childProcess.pid}`
|
|
|
|
);
|
|
|
|
process.exit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Kill a process by PID
|
|
|
|
* @param {number} pid
|
|
|
|
*/
|
|
|
|
function killProcessForce(pid) {
|
|
|
|
if (typeof pid !== "number") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
2024-12-04 05:28:32 +00:00
|
|
|
if (isWindows) {
|
2024-11-03 09:57:44 +00:00
|
|
|
execSync(`taskkill /F /PID ${pid} /T`);
|
|
|
|
} else {
|
|
|
|
execSync(`kill -9 ${pid}`);
|
|
|
|
}
|
|
|
|
} catch (error) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
` - ${colors.FgGreen}Started ${processesStrings.length} processes${colors.Reset}`
|
|
|
|
);
|
|
|
|
|
2024-12-04 06:57:15 +00:00
|
|
|
startProcesses();
|