UNPKG

@travetto/repo

Version:

Monorepo utilities

106 lines (90 loc) 4.45 kB
import type { ChildProcess } from 'node:child_process'; import { type ExecutionResult, Env, Util, ExecUtil, castTo } from '@travetto/runtime'; import { CliModuleUtil } from '@travetto/cli'; import type { IndexedModule } from '@travetto/manifest'; import { StyleUtil, Terminal, TerminalUtil } from '@travetto/terminal'; import { WorkPool } from '@travetto/worker'; const COLORS = ([...[ '#8787ff', '#87afff', '#87d7ff', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af87ff', '#afafd7', '#afafff', '#afd7af', '#afd7d7', '#afd7ff', '#afff87', '#afffaf', '#afffd7', '#afffff', '#d787ff', '#d7afaf', '#d7afd7', '#d7afff', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd787', '#ffd7af', '#ffd7d7', '#ffd7ff', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee' ] as const]).toSorted(() => Math.random() < .5 ? -1 : 1).map(color => StyleUtil.getStyle(color)); type ModuleRunConfig<T = ExecutionResult<string>> = { progressMessage?: (module: IndexedModule | undefined) => string; filter?: (module: IndexedModule) => boolean | Promise<boolean>; transformResult?: (module: IndexedModule, result: ExecutionResult<string>) => T; workerCount?: number; prefixOutput?: boolean; showStdout?: boolean; showStderr?: boolean; stableOutput?: boolean; }; const colorize = (value: string, idx: number): string => COLORS[idx % COLORS.length](value); /** * Tools for running commands across all modules of the monorepo */ export class RepoExecUtil { /** * Build equal sized prefix labels for outputting * @param modules * @returns */ static #buildPrefixes(modules: IndexedModule[]): Record<string, string> { const folders = modules.map(module => module.sourceFolder); const maxWidth = Math.max(...folders.map(folder => folder.length)); return Object.fromEntries(folders.map((folder, i) => [folder, colorize(folder.padStart(maxWidth, ' ').padEnd(maxWidth + 1), i)])); } /** * Run on all modules */ static async execOnModules<T = ExecutionResult>( mode: 'all' | 'workspace' | 'changed', operation: (module: IndexedModule) => ChildProcess, config: ModuleRunConfig<T> = {} ): Promise<Map<IndexedModule, T>> { config.showStdout = config.showStdout ?? (Env.DEBUG.isSet && !Env.DEBUG.isFalse); config.showStderr = config.showStderr ?? true; const transform = config.transformResult ?? ((module, result): T => castTo(result)); const workerCount = config.workerCount ?? WorkPool.DEFAULT_SIZE; const modules = await CliModuleUtil.findModules(mode); const results = new Map<IndexedModule, T>(); const processes = new Map<IndexedModule, ChildProcess>(); const prefixes = config.prefixOutput !== false ? this.#buildPrefixes(modules) : {}; const stdoutTerm = new Terminal(process.stdout); const stderrTerm = new Terminal(process.stderr); const work = WorkPool.runStreamProgress(async (module) => { try { if (!(await config.filter?.(module) === false)) { const prefix = prefixes[module.sourceFolder] ?? ''; const subProcess = operation(module); processes.set(module, subProcess); if (config.showStdout && subProcess.stdout) { ExecUtil.readLines(subProcess.stdout, line => stdoutTerm.writer.writeLine(`${prefix}${line.trimEnd()}`).commit() ); } if (config.showStderr && subProcess.stderr) { ExecUtil.readLines(subProcess.stderr, line => stderrTerm.writer.writeLine(`${prefix}${line.trimEnd()}`).commit() ); } const result = await ExecUtil.getResult(subProcess, { catch: true }); const output = transform(module, result); results.set(module, output); } return config.progressMessage?.(module) ?? module.name; } finally { processes.get(module!)?.kill(); } }, modules, modules.length, { max: workerCount, min: workerCount }); if (config.progressMessage && stdoutTerm.interactive) { await stdoutTerm.streamToBottom(Util.mapAsyncIterable(work, TerminalUtil.progressBarUpdater(stdoutTerm, { withWaiting: true }))); } else { for await (const _ of work) { // Ensure its all consumed } } return results; } }