run-scripts-util
Version:
Organize npm package.json scripts into groups of easy-to-manage commands (CLI tool designed for use in npm package.json scripts)
120 lines (118 loc) • 5.99 kB
JavaScript
//! run-scripts-util v1.3.8 ~~ https://github.com/center-key/run-scripts-util ~~ MIT License
import { cliArgvUtil } from 'cli-argv-util';
import { spawn, spawnSync } from 'node:child_process';
import chalk from 'chalk';
import fs from 'node:fs';
import log from 'fancy-log';
const arrow = chalk.gray.bold('→');
const name = chalk.gray('run-scripts');
const createLogger = (settings) => (...args) => !settings.quiet && log(name, ...args);
const runScripts = {
version: '1.3.8',
assertOk(ok, message) {
if (!ok)
throw new Error(`[run-scripts-util] ${message}`);
},
exec(group, options) {
const defaults = {
continueOnError: false,
only: null,
quiet: false,
verbose: false,
};
const settings = { ...defaults, ...options };
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
const commands = pkg.runScriptsConfig?.[group] ?? [pkg.scripts?.[group]];
const logger = createLogger(settings);
const isNotString = (value) => typeof value !== 'string';
const badGroup = !Array.isArray(commands) || commands.some(isNotString);
runScripts.assertOk(!badGroup, 'Cannot find commands: ' + group);
const execCommand = (step, command) => {
const startTime = Date.now();
if (!settings.quiet)
console.info();
const logItems = [chalk.white(group)];
if (settings.verbose)
logItems.push(chalk.yellow(step), arrow);
logger(...logItems, chalk.cyanBright(command.replace(/\s+/g, ' ')));
const task = spawnSync(command, { shell: true, stdio: 'inherit' });
const errorMessage = () => `Task: ${group} (step ${step}), Status: ${task.status}`;
if (task.status !== 0 && settings.continueOnError)
logger(chalk.red('ERROR'), chalk.white('-->'), errorMessage());
const stop = task.status !== 0 && !settings.continueOnError;
runScripts.assertOk(!stop, `${errorMessage()}, Command: ${command}`);
logger(...logItems, chalk.green('done'), chalk.white(`(${Date.now() - startTime}ms)`));
};
const skip = (step, command) => {
const active = settings.only === null || step === settings.only;
const commentedOut = command.startsWith('//');
if (commentedOut)
logger(chalk.yellow('skipping:'), command);
return !active || commentedOut;
};
const processCommand = (command, index) => !skip(index + 1, command) && execCommand(index + 1, command);
commands.forEach(processCommand);
},
execParallel(group, options) {
const defaults = {
continueOnError: false,
only: null,
quiet: false,
verbose: false,
};
const settings = { ...defaults, ...options };
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
const commands = pkg.runScriptsConfig?.[group] ?? [pkg.scripts?.[group]];
const isNotString = (value) => typeof value !== 'string';
const badGroup = !Array.isArray(commands) || commands.some(isNotString);
runScripts.assertOk(!badGroup, 'Cannot find commands: ' + group);
const logger = createLogger(settings);
const active = (step) => settings.only === null || step === settings.only;
const process = (step, command) => new Promise((resolve) => {
const start = Date.now();
const task = spawn(command, { shell: true, stdio: 'inherit' });
const pid = task.pid ?? null;
const logItems = [chalk.white(group), chalk.yellow(step)];
if (settings.verbose)
logItems.push(chalk.magenta('pid: ' + String(pid)), arrow);
logger(...logItems, chalk.cyanBright(command));
const processInfo = (code, ms) => ({ group, step, pid, start, code, ms });
task.on('close', (code) => resolve(processInfo(code, Date.now() - start)));
task.on('close', (code) => logger(...logItems, chalk.green('done'), chalk.white(`(code: ${code}, ${Date.now() - start}ms)`)));
});
const createProcess = (command, index) => active(index + 1) ? process(index + 1, command) : Promise.resolve(null);
logger(chalk.white(group), chalk.blue('--parallel'));
return Promise.all(commands.map(createProcess));
},
cli() {
const validFlags = ['continue-on-error', 'note', 'only', 'parallel', 'quiet', 'verbose'];
const cli = cliArgvUtil.parse(validFlags);
const groups = cli.params;
const invalidOnlyUse = cli.flagOn.only && cli.paramCount !== 1;
const error = cli.invalidFlag ? cli.invalidFlagMsg :
!cli.paramCount ? 'Must provide at lease one group of commands to run.' :
invalidOnlyUse ? 'The --only flag does not support multiple groups of commands.' :
null;
runScripts.assertOk(!error, error);
const logHeader = () => {
const version = chalk.gray('v' + runScripts.version);
const summary = chalk.gray(`(groups: ${groups.length})`);
console.info();
log(name, version, chalk.white(groups.join(', ')), summary);
};
if (!cli.flagOn.quiet)
logHeader();
const options = {
continueOnError: cli.flagOn.continueOnError,
only: cli.flagOn.only ? Number(cli.flagMap.only) : null,
quiet: cli.flagOn.quiet,
verbose: cli.flagOn.verbose,
};
const runGroup = (prevPromise, nextGroup) => prevPromise.then(() => runScripts.execParallel(nextGroup, options));
if (cli.flagOn.parallel)
groups.reduce(runGroup, Promise.resolve([]));
else
groups.forEach(group => runScripts.exec(group, options));
},
};
export { runScripts };