UNPKG

@deftomat/opinionated

Version:

Opinionated tooling for JavaScript & TypeScript projects.

116 lines (115 loc) 4.08 kB
import chalk from 'chalk'; import ora from 'ora'; import prettyMs from 'pretty-ms'; import stripAnsi from 'strip-ansi'; import { parentPort, Worker, workerData } from 'node:worker_threads'; import { ToolError, ToolWarning } from './errors.js'; const { bold, cyan, gray, green, red, yellow } = chalk; export async function step({ description, run, success = description }) { const spinner = ora({ text: cyan(description) }).start(); await delay(700); try { const startAt = Date.now(); const result = await run(); const endAt = Date.now(); const successText = typeof success === 'function' ? success(result) : success; spinner.stopAndPersist({ symbol: green('✔'), text: green(successText) + gray(` (${prettyMs(endAt - startAt)})`) }); return { result, hasWarning: false }; } catch (error) { if (ToolError.is(error)) { const [first, ...rest] = error.messages; spinner.stopAndPersist({ symbol: red('❌ '), text: red(first) }); rest.forEach(e => console.error(e)); throw process.exit(1); } else if (ToolWarning.is(error)) { const [first, ...rest] = error.messages; spinner.stopAndPersist({ symbol: yellow('⚠️ '), text: yellow(first) }); rest.forEach(e => console.error(e)); return { hasWarning: true }; } else { spinner.stopAndPersist({ symbol: red('❌ '), text: red(description) }); console.error(error); throw process.exit(1); } } } export function delay(time) { return new Promise(resolve => setTimeout(resolve, time)); } export function debug(...args) { if (process.env.DEBUG === 'true') { console.info(...args.map(arg => yellow(arg))); } } export function isNotNil(value) { return value != null; } export function populated(value) { if (value == null) return false; if (Buffer.isBuffer(value)) return value.length > 0; return value !== ''; } export function renderOnePackageWarning(context) { const packageName = context.packageSpec.get().name || context.packageRoot.split('/').pop(); const banner = asBanner([ bold(`Running in "${packageName}" sub-package...\n`), `Please keep in mind that to check the whole project,`, `you need to run this command in project's root directory!` ]); console.warn(''); console.warn(yellow.inverse(banner)); console.warn(''); } function asBanner(lines) { const normalized = lines.flatMap(line => line.split('\n')).map(line => ` ${line.trim()} `); const maxLength = normalized .map(stripAnsi) .map(line => line.length) .reduce((max, length) => (max > length ? max : length), 0); const content = normalized .map(line => line + spacing(maxLength - stripAnsi(line).length)) .join('\n'); return `${spacing(maxLength)}\n${content}\n${spacing(maxLength)}`; } function spacing(length) { return Array.from(Array(length)).fill(' ').join(''); } export function asWorkerMaster(workerFilename) { return ((...args) => { return new Promise((resolve, reject) => { const worker = new Worker(workerFilename, { workerData: args }); worker.on('message', ({ value, error }) => { if (error) { reject(error); } else { resolve(value); } }); worker.on('error', reject); worker.on('exit', code => { if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`)); }); }); }); } export async function runAsWorkerSlave(fn) { if (parentPort == null) throw Error('Unexpected process state!'); try { const value = await fn(...workerData); parentPort.postMessage({ value }); } catch (error) { parentPort.postMessage({ error }); } }