tools/task-runner/runner.mjs (123 lines of code) (raw):

#!/usr/bin/env node // force any plugins that use `chalk` to output in full colour process.env.FORCE_COLOR = true; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { Listr } from 'listr2'; import chalk from 'chalk'; import figures from 'figures'; import uniq from 'lodash.uniq'; import { VerboseRenderer } from './run-task-verbose-formater.mjs'; // name of the tasks directory const tasksDirectory = '__tasks__'; // use yargs to get a useful CLI const { debug: IS_DEBUG, verbose: IS_VERBOSE, stdout: IS_STDOUT, _: TASKS, } = yargs(hideBin(process.argv)) .option('debug', { demand: false, describe: 'Log everything there is to log.', type: 'boolean', nargs: 0, }) .option('verbose', { demand: false, describe: 'Log everything there is to log with a simple format for the output.', type: 'boolean', nargs: 0, }) .option('stdout', { demand: false, describe: 'Log all stdout once the tasks are finished (errors are logged by default).', type: 'boolean', nargs: 0, }) .usage('Usage: $0 <task> [<task>] [--dev]') .command('task', `Run a task defined in '${tasksDirectory}'.`) .example('$0 copy/index.js', 'Run all the copy tasks.') .example('$0 javascript/copy.js', 'Run the javascript copy task.') .example('$0 compile/index.dev.js', 'Run the compile dev copy task.') .example( '$0 compile/javascript/copy.js compile/css/copy.js', 'Run the javascript copy and css copy tasks.', ) .demand(1) .help() .alias('h', 'help') .version() .alias('v', 'version').argv; // if this is true, we log as much as we can const VERBOSE = IS_VERBOSE || IS_DEBUG; // look here for tasks that come in from yargs const taskSrc = path.resolve( path.dirname(fileURLToPath(import.meta.url)), '..', tasksDirectory, ); /** * resolve the tasks from yargs to actual files */ const getTasksFromModule = async (taskName) => { try { const modulePath = path.resolve(taskSrc, taskName); return (await import(modulePath)).default; } catch (e) { // we can't find any modules, or something else has gone wrong in resolving it // so output an erroring task return { description: `${chalk.red(taskName)} failed:`, task: () => Promise.reject(e), }; } }; /** get a list of the tasks we're going to run */ const taskModules = await Promise.all(TASKS.map(getTasksFromModule)); // run them! new Listr(taskModules, { collapse: true, renderer: IS_VERBOSE ? VerboseRenderer : 'default', concurrent: VERBOSE ? false : true, }) .run({ // we're adding these to the [listr context](https://listr2.kilic.dev/listr/context.html) messages: [], stdouts: [], verbose: VERBOSE, }) .catch((e) => { // something went wrong, so log whatever we have if (!e.stderr && !e.stdout) console.log(e); if (e.stderr) console.error(`\n${e.stderr.trim()}`); if (e.stdout) console.log(`\n${e.stdout.trim()}`); return Object.assign(e.context, { error: true }); }) .then((ctx) => { if (IS_STDOUT) ctx.stdouts.forEach((stdout) => console.log(stdout.toString().trim()), ); if (ctx.messages.length) { console.log(''); uniq(ctx.messages).forEach((message) => console.log( chalk.dim( `${figures.arrowRight} ${message .split('\n') .join('\n ')}`, ), ), ); } if (ctx.error) { console.log(''); // if something's gone wrong, fail hard process.exit(1); } });