Skip to content

Instantly share code, notes, and snippets.

@Stanko
Last active June 13, 2024 15:29
Show Gist options
  • Save Stanko/99ae7d024eee36b2c6a236ea7d00cd22 to your computer and use it in GitHub Desktop.
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/
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');
}
}
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