Last active
June 13, 2024 15:29
-
-
Save Stanko/99ae7d024eee36b2c6a236ea7d00cd22 to your computer and use it in GitHub Desktop.
Node.js script to run multiple commands in parallel. Terminates all of them if any of the commands fails. Blog post - https://muffinman.io/blog/node-script-to-run-multiple-commands-in-parallel/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { spawn } from 'child_process'; | |
export class Runner { | |
tasks = []; | |
exiting = false; | |
colors = [ | |
'\x1b[33m', // yellow | |
'\x1b[34m', // blue | |
'\x1b[35m', // magenta | |
'\x1b[36m', // cyan | |
]; | |
/** | |
* Create a new runner. | |
* @param {Object[]} tasks - The x value. | |
* @param {string} tasks[].name - The name of the task. | |
* @param {string} tasks[].command - The command to run including arguments. | |
*/ | |
constructor(tasks) { | |
tasks.forEach((task, index) => { | |
// Split the command into command and arguments | |
const args = task.command.split(' '); | |
const command = args.shift(); | |
// Color the process prefix for output | |
const color = this.colors[index % this.colors.length]; | |
const reset = '\x1b[0m'; | |
const name = `${color}${task.name}:${reset}`; | |
// Spawn the child process | |
const childProcess = spawn(command, args); | |
// Prefix the output and write to stdout | |
childProcess.stdout.on('data', (data) => { | |
process.stdout.write(this.addPrefix(data.toString(), name)); | |
}); | |
// Prefix the error output and write to stderr | |
childProcess.stderr.on('data', (data) => { | |
process.stderr.write(this.addPrefix(data.toString(), name)); | |
}); | |
// When any process closes, start termination of all processes | |
childProcess.on('close', (code, signal) => { | |
if (code !== null) { | |
console.log(`${name} exited with code ${code}`); | |
} else if (signal) { | |
console.log(`${name} was killed with signal ${signal}`); | |
} | |
this.exit(); | |
}); | |
// If any process fails to start, terminate all processes | |
childProcess.on('error', (error) => { | |
console.error(`${name} failed to start: ${error.message}`); | |
this.exit(); | |
}); | |
// Add task to the list | |
this.tasks.push({ | |
name, | |
childProcess, | |
}); | |
}); | |
} | |
// Terminates all processes | |
exit() { | |
if (!this.exiting) { | |
this.exiting = true; | |
this.tasks.forEach((task) => { | |
if (!task.childProcess.killed) { | |
task.childProcess.kill(); | |
} | |
}); | |
} | |
} | |
// Add prefix to each line of the text | |
// Skips empty lines | |
addPrefix(text, prefix) { | |
return text | |
.split('\n') | |
.map((line) => (line ? `${prefix} ${line}` : '')) | |
.join('\n'); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Runner } from './runner.js'; | |
const tasks = [ | |
{ name: 'sass', command: 'npm run sass' }, | |
{ name: 'build', command: 'npm run build' }, | |
]; | |
new Runner(tasks); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment