@deftomat/opinionated
Version:
Opinionated tooling for JavaScript & TypeScript projects.
116 lines (115 loc) • 4.08 kB
JavaScript
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 });
}
}