diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..50ccfdf --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +registry=https://git.tben.me/api/packages/Moduletrace/npm/ +//git.tben.me/api/packages/Moduletrace/npm/:_authToken=${GITBEN_NPM_TOKEN} diff --git a/README.md b/README.md index b774728..94f6f9e 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,109 @@ -# Run multiple concurrent processes - -Do you run an application that needs multiple processes? Like `npm run dev` and `npm run tailwind`. Typically in a dev environment you would need to have more than one terminal open if you're running more than on processes. But with batch run you only need one terminal to run all processes concurrently. - -## Installation - -You can install and run this package directly by running: - -```bash -npx batchrun "npm run dev, npm run tailwind" -``` - -To install this module globally simply run: - -```bash -npm install -g batchrun -``` - -To install this module locally(for just the project) run: - -```bash -npm install batchrun -``` - -_NOTE_: You will have to append `npx` to your command for just a local installation. - -## Requirements - -For this package to work you need an argument of strings in the terminal command, or a `batchrun.config.json` file at the root of your project directory, which is simply an array of strings, each string being the command to run. Example: - -```json -["node index.js", "npm run tailwind"] -``` - -More entries mean more processes. - -# Usage - -To fire up the process without a `batchrun.config.json` file simply run: - -```bash -batchrun "npm run dev, npm run tailwind" -``` - -If you did not install the package globally, run: - -```bash -npx batchrun "npm run dev, npm run tailwind" -``` - -If you already have a `batchrun.config.json` file just run - -```bash -batchrun -``` - -# Process actions - -When your process is up and running, you can perform a few more actions to your running processes. - -### Reload all processes - -To reload all processes, while the process is active in your terminal type - -```bash -reload -``` - -Press `Enter` after tyring this. This will reload all the processes you started. Alternatively, you can simply press `Ctrl+R`: this does the same thing. `restart` amd `reboot` commands work also. - -### Reload processes - -You can also target a single process and reload it. This will need an index appended to the `reload` command. Example: - -```bash -- Processes Started ... -... -reload 0 -``` - -This will reload the first command in your list of commands. You can add more indexes by separating them with a comma(,): Example: - -```bash -- Processes Started ... -... -reload 0,3,4 -``` - -_NOTE_: Do not add a space between the commands or else the `reload` command won't work. - -### Kill processes - -To kill all processes simply type: - -```bash -- Processes Started ... -... -kill -``` - -This is the equivalent of pressing `Ctrl+C`. To kill specific processes just follow the same convention as the `reload` command. Example: - -```bash -- Processes Started ... -... -kill 1,2 -``` - -To restart a killed process simple use the `reload` command again. +# Run multiple concurrent processes + +Do you run an application that needs multiple processes? Like `npm run dev` and `npm run tailwind`. Typically in a dev environment you would need to have more than one terminal open if you're running more than on processes. But with batch run you only need one terminal to run all processes concurrently. + +## Installation + +You can install and run this package directly by running: + +```bash +npx batchrun "npm run dev, npm run tailwind" +``` + +To install this module globally simply run: + +```bash +npm install -g batchrun +``` + +To install this module locally(for just the project) run: + +```bash +npm install batchrun +``` + +_NOTE_: You will have to append `npx` to your command for just a local installation. + +## Requirements + +For this package to work you need an argument of strings in the terminal command, or a `batchrun.config.json` file at the root of your project directory, which is simply an array of strings, each string being the command to run. Example: + +```json +["node index.js", "npm run tailwind"] +``` + +More entries mean more processes. + +# Usage + +To fire up the process without a `batchrun.config.json` file simply run: + +```bash +batchrun "npm run dev, npm run tailwind" +``` + +If you did not install the package globally, run: + +```bash +npx batchrun "npm run dev, npm run tailwind" +``` + +If you already have a `batchrun.config.json` file just run + +```bash +batchrun +``` + +# Process actions + +When your process is up and running, you can perform a few more actions to your running processes. + +### Reload all processes + +To reload all processes, while the process is active in your terminal type + +```bash +reload +``` + +Press `Enter` after tyring this. This will reload all the processes you started. Alternatively, you can simply press `Ctrl+R`: this does the same thing. `restart` amd `reboot` commands work also. + +### Reload processes + +You can also target a single process and reload it. This will need an index appended to the `reload` command. Example: + +```bash +- Processes Started ... +... +reload 0 +``` + +This will reload the first command in your list of commands. You can add more indexes by separating them with a comma(,): Example: + +```bash +- Processes Started ... +... +reload 0,3,4 +``` + +_NOTE_: Do not add a space between the commands or else the `reload` command won't work. + +### Kill processes + +To kill all processes simply type: + +```bash +- Processes Started ... +... +kill +``` + +This is the equivalent of pressing `Ctrl+C`. To kill specific processes just follow the same convention as the `reload` command. Example: + +```bash +- Processes Started ... +... +kill 1,2 +``` + +To restart a killed process simple use the `reload` command again. diff --git a/console-colors.js b/console-colors.js index bec5d22..708acf6 100644 --- a/console-colors.js +++ b/console-colors.js @@ -1,31 +1,31 @@ -const colors = { - Reset: "\x1b[0m", - Bright: "\x1b[1m", - Dim: "\x1b[2m", - Underscore: "\x1b[4m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - Hidden: "\x1b[8m", - - FgBlack: "\x1b[30m", - FgRed: "\x1b[31m", - FgGreen: "\x1b[32m", - FgYellow: "\x1b[33m", - FgBlue: "\x1b[34m", - FgMagenta: "\x1b[35m", - FgCyan: "\x1b[36m", - FgWhite: "\x1b[37m", - FgGray: "\x1b[90m", - - BgBlack: "\x1b[40m", - BgRed: "\x1b[41m", - BgGreen: "\x1b[42m", - BgYellow: "\x1b[43m", - BgBlue: "\x1b[44m", - BgMagenta: "\x1b[45m", - BgCyan: "\x1b[46m", - BgWhite: "\x1b[47m", - BgGray: "\x1b[100m", -}; - -module.exports = colors; +const colors = { + Reset: "\x1b[0m", + Bright: "\x1b[1m", + Dim: "\x1b[2m", + Underscore: "\x1b[4m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + Hidden: "\x1b[8m", + + FgBlack: "\x1b[30m", + FgRed: "\x1b[31m", + FgGreen: "\x1b[32m", + FgYellow: "\x1b[33m", + FgBlue: "\x1b[34m", + FgMagenta: "\x1b[35m", + FgCyan: "\x1b[36m", + FgWhite: "\x1b[37m", + FgGray: "\x1b[90m", + + BgBlack: "\x1b[40m", + BgRed: "\x1b[41m", + BgGreen: "\x1b[42m", + BgYellow: "\x1b[43m", + BgBlue: "\x1b[44m", + BgMagenta: "\x1b[45m", + BgCyan: "\x1b[46m", + BgWhite: "\x1b[47m", + BgGray: "\x1b[100m", +}; + +module.exports = colors; diff --git a/index.js b/index.js index b1e3e5e..bbe63c8 100644 --- a/index.js +++ b/index.js @@ -1,265 +1,300 @@ -#! /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"); - -//////////////////////////////////////////// -//////////////////////////////////////////// -//////////////////////////////////////////// - -/** @type {string[]} */ -const processesStrings = []; - -/** @type {ChildProcess[]} */ -let processes = []; - -//////////////////////////////////////////// -//////////////////////////////////////////// -//////////////////////////////////////////// - -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) { - if (input?.match(/^(reload|restart|reboot)$/i)) { - console.log(` - ${colors.FgBlue}Reloading processes ...${colors.Reset}`); - restartAll(); - } else if (input?.match(/^(reload|restart|reboot) \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.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(); - } -}); - -process.on("exit", (code) => { - console.log(` - ${colors.FgBlue}Process exited with code ${code}${colors.Reset}`); - rl.close(); -}); - -//////////////////////////////////////////// -//////////////////////////////////////////// -//////////////////////////////////////////// - -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(), - shell: process.platform.match(/win/i) ? "bash.exe" : undefined, - stdio: ["pipe", "inherit", "inherit"], -}; - -/** - * 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(); - if (targetProcess) { - const childProcess = spawn(targetProcess, processStringArray, spawnOptions); - if (childProcess) { - processes.push(childProcess); - - // childProcess.stdin?.on("keypress", (character, key) => { - // if (key.ctrl && key.name === "r") { - // console.log(` - ${colors.FgBlue}Reloading processes ...${colors.Reset}`); - // restartAll(); - // } - // }); - - // childProcess.on("error", (error) => { - // console.log(` - ${colors.FgRed}Error:${colors.Reset} ${error.message}`); - // killOne(i.toString()); - // }); - - // childProcess.on("exit", (code, signal) => { - // console.log(` - ${colors.FgRed}Error:${colors.Reset} Process ${i} exited with code ${code} and signal ${signal}`); - // killOne(i.toString()); - // }); - - // childProcess.on("message", (code, signal) => { - // console.log(` - ${colors.FgRed}Error:${colors.Reset} Process ${i} exited with code ${code} and signal ${signal}`); - // killOne(i.toString()); - // }); - - // childProcess.stdin?.pipe(process.stdin); - } 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 { - if (process.platform.match(/win/i)) { - execSync(`taskkill /F /PID ${pid} /T`); - } else { - execSync(`kill -9 ${pid}`); - } - } catch (error) {} -} - -console.log(` - ${colors.FgGreen}Started ${processesStrings.length} processes${colors.Reset}`); -startProcesses(); +#! /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"); + +//////////////////////////////////////////// +//////////////////////////////////////////// +//////////////////////////////////////////// + +/** @type {string[]} */ +const processesStrings = []; + +/** @type {ChildProcess[]} */ +let processes = []; + +//////////////////////////////////////////// +//////////////////////////////////////////// +//////////////////////////////////////////// + +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) { + if (input?.match(/^(reload|restart|reboot)$/i)) { + console.log( + ` - ${colors.FgBlue}Reloading processes ...${colors.Reset}` + ); + restartAll(); + } else if (input?.match(/^(reload|restart|reboot) \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.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(); + } +}); + +process.on("exit", (code) => { + console.log( + ` - ${colors.FgBlue}Process exited with code ${code}${colors.Reset}` + ); + rl.close(); +}); + +//////////////////////////////////////////// +//////////////////////////////////////////// +//////////////////////////////////////////// + +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(), + shell: process.platform.match(/win/i) ? "bash.exe" : undefined, + stdio: ["pipe", "inherit", "inherit"], + 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(); + if (targetProcess) { + const childProcess = spawn( + targetProcess, + processStringArray, + spawnOptions + ); + if (childProcess) { + 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 { + if (process.platform.match(/win/i)) { + execSync(`taskkill /F /PID ${pid} /T`); + } else { + execSync(`kill -9 ${pid}`); + } + } catch (error) {} +} + +console.log( + ` - ${colors.FgGreen}Started ${processesStrings.length} processes${colors.Reset}` +); +startProcesses(); + +setInterval(() => { + console.log( + `Turbo Sync Running for ${process.uptime().toLocaleString()}s ...` + ); +}, 60000); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e44c056 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,37 @@ +{ + "name": "batchrun", + "version": "1.0.7", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "batchrun", + "version": "1.0.7", + "license": "MIT", + "bin": { + "batch-run": "index.js", + "batchrun": "index.js" + }, + "devDependencies": { + "@types/node": "^22.8.7" + } + }, + "node_modules/@types/node": { + "version": "22.8.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.7.tgz", + "integrity": "sha512-LidcG+2UeYIWcMuMUpBKOnryBWG/rnmOHQR5apjn8myTQcx3rinFRn7DcIFhMnS0PPFSC6OafdIKEad0lj6U0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json index 493b3ef..dec8c50 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,8 @@ "monitor" ], "author": "Benjamin Toby", - "license": "MIT" + "license": "MIT", + "devDependencies": { + "@types/node": "^22.8.7" + } }