From 6a5a923f439c6eb3246074042347b249c6291321 Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Thu, 28 Mar 2024 08:01:19 -0700 Subject: [PATCH 01/17] refactor(logger): capture all output --- commands/build.ts | 2 +- modules/logger/src/LogStep.ts | 27 +++++++++---------------- modules/logger/src/Logger.ts | 14 +++++++++++++ modules/logger/src/index.ts | 2 +- modules/onerepo/src/core/tasks/tasks.ts | 2 +- modules/subprocess/src/index.ts | 11 ++++++++-- 6 files changed, 36 insertions(+), 22 deletions(-) diff --git a/commands/build.ts b/commands/build.ts index 6cd38faa..62ca93af 100644 --- a/commands/build.ts +++ b/commands/build.ts @@ -45,7 +45,7 @@ export const handler: Handler = async function handler(argv, { getWorkspac for (const workspace of workspaces) { if (workspace.private) { - buildableStep.warn(`Not building \`${workspace.name}\` because it is private`); + // buildableStep.warn(`Not building \`${workspace.name}\` because it is private`); continue; } diff --git a/modules/logger/src/LogStep.ts b/modules/logger/src/LogStep.ts index 46fde1aa..c8fadced 100644 --- a/modules/logger/src/LogStep.ts +++ b/modules/logger/src/LogStep.ts @@ -254,9 +254,7 @@ export class LogStep { info(contents: unknown) { this.#onMessage('info'); this.hasInfo = true; - if (this.verbosity >= 1) { - this.#writeStream(this.#prefix(prefix.INFO, stringify(contents))); - } + this.#writeStream(this.#prefix(prefix.INFO, stringify(contents)), this.verbosity >= 1); } /** @@ -278,9 +276,7 @@ export class LogStep { error(contents: unknown) { this.#onMessage('error'); this.hasError = true; - if (this.verbosity >= 1) { - this.#writeStream(this.#prefix(prefix.ERR, stringify(contents))); - } + this.#writeStream(this.#prefix(prefix.ERR, stringify(contents)), this.verbosity >= 1); } /** @@ -302,9 +298,7 @@ export class LogStep { warn(contents: unknown) { this.#onMessage('warn'); this.hasWarning = true; - if (this.verbosity >= 2) { - this.#writeStream(this.#prefix(prefix.WARN, stringify(contents))); - } + this.#writeStream(this.#prefix(prefix.WARN, stringify(contents)), this.verbosity >= 2); } /** @@ -326,9 +320,7 @@ export class LogStep { log(contents: unknown) { this.#onMessage('log'); this.hasLog = true; - if (this.verbosity >= 3) { - this.#writeStream(this.#prefix(this.name ? prefix.LOG : '', stringify(contents))); - } + this.#writeStream(this.#prefix(this.name ? prefix.LOG : '', stringify(contents)), this.verbosity >= 3); } /** @@ -349,9 +341,7 @@ export class LogStep { */ debug(contents: unknown) { this.#onMessage('debug'); - if (this.verbosity >= 4) { - this.#writeStream(this.#prefix(prefix.DBG, stringify(contents))); - } + this.#writeStream(this.#prefix(prefix.DBG, stringify(contents)), this.verbosity >= 4); } /** @@ -375,8 +365,11 @@ export class LogStep { } } - #writeStream(line: string) { - this.#buffer.write(ensureNewline(line)); + #writeStream(line: string, toBuffer: boolean = true) { + if (toBuffer) { + this.#buffer.write(ensureNewline(line)); + } + if (this.#active) { const lines = line.split('\n'); const lastThree = lines.slice(-3); diff --git a/modules/logger/src/Logger.ts b/modules/logger/src/Logger.ts index e8567a26..656971ec 100644 --- a/modules/logger/src/Logger.ts +++ b/modules/logger/src/Logger.ts @@ -34,6 +34,10 @@ export type LoggerOptions = { * Advanced – override the writable stream in order to pipe logs elsewhere. Mostly used for dependency injection for `@onerepo/test-cli`. */ stream?: Writable; + /** + * @experimental + */ + captureAll?: boolean; }; const frames: Array = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; @@ -64,6 +68,7 @@ export class Logger { #hasWarning = false; #hasInfo = false; #hasLog = false; + #captureAll = false; /** * @internal @@ -74,6 +79,8 @@ export class Logger { this.#stream = options.stream ?? process.stderr; this.#updater = createLogUpdate(this.#stream); + this.#captureAll = !!options.captureAll; + this.#defaultLogger = new LogStep('', { onEnd: this.#onEnd, onMessage: this.#onMessage, @@ -90,6 +97,13 @@ export class Logger { setCurrent(this); } + /** + * @experimental + */ + get captureAll() { + return this.#captureAll; + } + /** * Get the logger's verbosity level */ diff --git a/modules/logger/src/index.ts b/modules/logger/src/index.ts index 1f96fcbc..d88c65a2 100644 --- a/modules/logger/src/index.ts +++ b/modules/logger/src/index.ts @@ -93,7 +93,7 @@ export async function stepWrapper( export function bufferSubLogger(step: LogStep): { logger: Logger; end: () => Promise } { const logger = getLogger(); const buffer = new LogBuffer(); - const subLogger = new Logger({ verbosity: logger.verbosity, stream: buffer }); + const subLogger = new Logger({ verbosity: logger.verbosity, stream: buffer, captureAll: true }); function proxyChunks(chunk: Buffer) { if (subLogger.hasError) { step.error(() => chunk.toString().trimEnd()); diff --git a/modules/onerepo/src/core/tasks/tasks.ts b/modules/onerepo/src/core/tasks/tasks.ts index ba3a1501..6845c541 100644 --- a/modules/onerepo/src/core/tasks/tasks.ts +++ b/modules/onerepo/src/core/tasks/tasks.ts @@ -280,7 +280,7 @@ function singleTaskToSpec( cmd === '$0' && logger.verbosity ? `-${'v'.repeat(logger.verbosity)}` : '', ].filter(Boolean) as Array; - const name = `${command.replace(/^\$0 /, `${cliName} `)} (${workspace.name})`; + const name = `${command.replace(/^\$0 /, `${cliName} `)}${!workspace.isRoot ? ` (${workspace.name})` : ''}`; let fn: PromiseFn | undefined; if (cmd === '$0') { diff --git a/modules/subprocess/src/index.ts b/modules/subprocess/src/index.ts index e9217383..bffc3a5f 100644 --- a/modules/subprocess/src/index.ts +++ b/modules/subprocess/src/index.ts @@ -115,7 +115,7 @@ export async function run(options: RunSpec): Promise<[string, string]> { detail: { description: 'Spawned subprocess', subprocess: { ...withoutLogger, opts } }, }); - if (options.opts?.stdio === 'inherit') { + if (!logger.captureAll && options.opts?.stdio === 'inherit') { logger.pause(); } @@ -150,7 +150,14 @@ export async function run(options: RunSpec): Promise<[string, string]> { ${JSON.stringify(withoutLogger, null, 2)}\n${process.env.ONEREPO_ROOT ?? process.cwd()}\n`, ); - const subprocess = start(options); + const subprocess = start({ + ...options, + opts: { + ...options.opts, + env: { ...options.opts?.env, FORCE_COLOR: !process.env.NO_COLOR ? '1' : undefined }, + stdio: logger.captureAll ? 'pipe' : options.opts?.stdio, + }, + }); subprocess.on('error', (error) => { if (!options.skipFailures) { From 89a106065570cf01b288322fd97b3041ec8d92eb Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Sun, 31 Mar 2024 08:45:57 -0700 Subject: [PATCH 02/17] update tests --- modules/onerepo/src/core/tasks/__tests__/tasks.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/onerepo/src/core/tasks/__tests__/tasks.test.ts b/modules/onerepo/src/core/tasks/__tests__/tasks.test.ts index e5284c51..679f8ec5 100644 --- a/modules/onerepo/src/core/tasks/__tests__/tasks.test.ts +++ b/modules/onerepo/src/core/tasks/__tests__/tasks.test.ts @@ -245,7 +245,7 @@ describe('handler', () => { args: ['"deployroot"'], cmd: 'echo', meta: { name: 'fixture-root', slug: 'fixture-root' }, - name: 'echo "deployroot" (fixture-root)', + name: 'echo "deployroot"', opts: { cwd: '.' }, }, ], @@ -284,7 +284,7 @@ describe('handler', () => { args: ['"deployroot"'], cmd: 'echo', meta: { name: 'fixture-root', slug: 'fixture-root' }, - name: 'echo "deployroot" (fixture-root)', + name: 'echo "deployroot"', opts: { cwd: '.' }, }, ], From a91b73ba2fe1fa0c5f16e50179e3b49e94a55fa7 Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Mon, 1 Apr 2024 08:11:01 -0700 Subject: [PATCH 03/17] streams checkpoint --- modules/logger/src/LogBuffer.ts | 207 +++++++++++++++++- modules/logger/src/LogStep.ts | 62 +++--- modules/logger/src/Logger.ts | 200 ++++++++++------- modules/logger/src/index.ts | 24 +- .../src/core/tasks/__tests__/tasks.test.ts | 2 +- modules/onerepo/src/setup/setup.ts | 5 + 6 files changed, 384 insertions(+), 116 deletions(-) diff --git a/modules/logger/src/LogBuffer.ts b/modules/logger/src/LogBuffer.ts index 32f8c0e7..bec9fbf4 100644 --- a/modules/logger/src/LogBuffer.ts +++ b/modules/logger/src/LogBuffer.ts @@ -1,16 +1,219 @@ -import { Duplex } from 'node:stream'; +import { Duplex, Transform } from 'node:stream'; +import pc from 'picocolors'; + +type BufferOptions = { + name: string; + description?: string; + onEnd: (step: LogBuffer) => Promise; +}; export class LogBuffer extends Duplex { + name?: string; + #hasError: boolean = false; + #startMark: string; + #onEnd: BufferOptions['onEnd']; + + isPiped: boolean = false; + + constructor({ description, name, onEnd }: BufferOptions) { + super({ decodeStrings: false }); + + this.#startMark = name || `${performance.now()}`; + performance.mark(`onerepo_start_${this.#startMark}`, { + detail: description, + }); + + this.name = name; + this.#onEnd = onEnd; + this.#write('start', name); + } + _read() {} // eslint-disable-next-line @typescript-eslint/no-unused-vars - _write(chunk: string, encoding = 'utf8', callback: () => void) { + _write(chunk: string | Buffer, encoding = 'utf8', callback: () => void) { + // this.push( + // typeof chunk === 'string' || chunk instanceof Buffer ? chunk : `${this.#prefix(prefix[chunk.type], chunk.line)}`, + // ); this.push(chunk); callback(); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + // _transform(chunk: string | { type: LineType; line: string }, encoding = 'utf8', callback: () => void) { + // // this.push(typeof chunk); + // this.push(typeof chunk === 'string' ? chunk : `${this.#prefix(prefix[chunk.type], chunk.line)}`); + // callback(); + // } + _final(callback: () => void) { this.push(null); callback(); } + + #write(type: LineType, contents: unknown) { + this.write( + Buffer.from( + JSON.stringify({ + type, + contents: stringify(contents), + group: this.name, + }), + ), + ); + } + + error(contents: unknown) { + this.#hasError = true; + this.#write('error', contents); + } + + warn(contents: unknown) { + this.#write('warn', contents); + } + + info(contents: unknown) { + this.#write('info', contents); + } + + log(contents: unknown) { + this.#write('log', contents); + } + + debug(contents: unknown) { + this.#write('debug', contents); + } + + timing(start: string, end: string) { + // if (this.verbosity >= 5) { + const [startMark] = performance.getEntriesByName(start); + const [endMark] = performance.getEntriesByName(end); + if (!startMark || !endMark) { + this.warn(`Unable to log timing. Missing either mark ${start} → ${end}`); + return; + } + this.#write( + 'timing', + `${startMark.name} → ${endMark.name}: ${Math.round(endMark.startTime - startMark.startTime)}ms`, + ); + // } + } + + // @ts-expect-error + async end() { + const endMark = performance.mark(`onerepo_end_${this.#startMark}`); + const [startMark] = performance.getEntriesByName(`onerepo_start_${this.#startMark}`); + + // TODO: jest.useFakeTimers does not seem to be applying to performance correctly + const duration = + !startMark || process.env.NODE_ENV === 'test' ? 0 : Math.round(endMark.startTime - startMark.startTime); + const contents = this.name + ? pc.dim(`${duration}ms`) + : `Completed${this.#hasError ? ' with errors' : ''} ${pc.dim(`${duration}ms`)}`; + this.#write('end', contents); + this.emit('end'); + this.emit('close'); + } +} + +const prefix: Record = { + // FAIL: pc.red('✘'), + // SUCCESS: pc.green('✔'), + timing: pc.red('⏳'), + start: ' ┌ ', + end: ' └ ', + error: pc.red(pc.bold('ERR ')), + warn: pc.yellow(pc.bold('WRN ')), + log: pc.cyan(pc.bold('LOG ')), + debug: pc.magenta(pc.bold('DBG ')), + info: pc.blue(pc.bold('INFO ')), +}; + +export type LineType = 'start' | 'end' | 'error' | 'warn' | 'info' | 'log' | 'debug' | 'timing'; + +type Options = { + verbosity: number; +}; + +export class LogBufferToString extends Transform { + #verbosity: number; + + constructor({ verbosity }: Options) { + super({ decodeStrings: false }); + this.#verbosity = verbosity; + } + + _transform( + chunk: Buffer, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + encoding = 'utf8', + callback: () => void, + ) { + const data = JSON.parse(chunk.toString()) as { type: LineType; contents: string; group?: string }; + // console.log(chunk.toString()); + if (typeMinVerbosity[data.type] <= this.#verbosity) { + this.push(ensureNewline(`${this.#prefix(data.type, data.group, stringify(data.contents))}`)); + } + callback(); + } + + _final(callback: () => void) { + this.push(null); + callback(); + } + + #prefix(type: LineType, group: string | undefined, output: string) { + if (type === 'end') { + return `${!group ? '◼︎ ' : prefix[type]}${output}`; + } + if (type === 'start') { + return `${!group ? '➤ ' : prefix[type]}${output}`; + } + return output + .split('\n') + .map((line) => ` ${group ? '│ ' : ''}${prefix[type]}${line}`) + .join('\n'); + } +} + +const typeMinVerbosity: Record = { + start: 1, + end: 1, + error: 1, + info: 1, + warn: 2, + log: 3, + debug: 5, + timing: 6, +}; + +function stringify(item: unknown): string { + if (typeof item === 'string') { + return item.replace(/^\n+/, '').replace(/\n*$/g, ''); + } + + if ( + Array.isArray(item) || + (typeof item === 'object' && item !== null && item.constructor === Object) || + item === null + ) { + return JSON.stringify(item, null, 2); + } + + if (item instanceof Date) { + return item.toISOString(); + } + + if (typeof item === 'function' && item.length === 0) { + return stringify(item()); + } + + return `${String(item)}`; +} + +function ensureNewline(str: string): string { + if (/^\S*$/.test(str)) { + return ''; + } + return str.endsWith('\n') ? str : str.replace(/\n*$/g, '\n'); } diff --git a/modules/logger/src/LogStep.ts b/modules/logger/src/LogStep.ts index c8fadced..78fb5b77 100644 --- a/modules/logger/src/LogStep.ts +++ b/modules/logger/src/LogStep.ts @@ -1,12 +1,12 @@ import { performance } from 'node:perf_hooks'; import type { Writable } from 'node:stream'; import pc from 'picocolors'; +import type { LineType } from './LogBuffer'; import { LogBuffer } from './LogBuffer'; type StepOptions = { verbosity: number; onEnd: (step: LogStep) => Promise; - onMessage: (type: 'error' | 'warn' | 'info' | 'log' | 'debug') => void; stream?: Writable; description?: string; writePrefixes?: boolean; @@ -43,8 +43,7 @@ export class LogStep { #stream: Writable; #active = false; #onEnd: StepOptions['onEnd']; - #onMessage: StepOptions['onMessage']; - #lastThree: Array = []; + #lastThree: Array<{ type: LineType; line: string }> = []; #writing: boolean = false; #writePrefixes: boolean = true; #startMark: string; @@ -69,7 +68,7 @@ export class LogStep { /** * @internal */ - constructor(name: string, { onEnd, onMessage, verbosity, stream, description, writePrefixes }: StepOptions) { + constructor(name: string, { onEnd, verbosity, stream, description, writePrefixes }: StepOptions) { this.#startMark = name || `${performance.now()}`; performance.mark(`onerepo_start_${this.#startMark}`, { detail: description, @@ -77,16 +76,15 @@ export class LogStep { this.#verbosity = verbosity; this.#name = name; this.#onEnd = onEnd; - this.#onMessage = onMessage; - this.#buffer = new LogBuffer({}); + this.#buffer = new LogBuffer({ name }); this.#stream = stream ?? process.stderr; this.#writePrefixes = writePrefixes ?? true; if (this.name) { - if (process.env.GITHUB_RUN_ID) { - this.#writeStream(`::group::${this.name}\n`); - } - this.#writeStream(this.#prefixStart(this.name)); + // if (process.env.GITHUB_RUN_ID) { + // this.#writeStream(`::group::${this.name}\n`); + // } + this.#writeStream('start', this.name); } } @@ -116,8 +114,8 @@ export class LogStep { * * @internal */ - get status(): Array { - return [this.#prefixStart(this.name), ...this.#lastThree]; + get status(): Array<{ type: LineType; line: string }> { + return this.#lastThree; } /** @@ -204,10 +202,10 @@ export class LogStep { const text = this.name ? pc.dim(`${duration}ms`) : `Completed${this.hasError ? ' with errors' : ''} ${pc.dim(`${duration}ms`)}`; - this.#writeStream(ensureNewline(this.#prefixEnd(`${this.hasError ? prefix.FAIL : prefix.SUCCESS} ${text}`))); - if (this.name && process.env.GITHUB_RUN_ID) { - this.#writeStream('::endgroup::\n'); - } + this.#writeStream('end', ensureNewline(`${this.hasError ? prefix.FAIL : prefix.SUCCESS} ${text}`)); + // if (this.name && process.env.GITHUB_RUN_ID) { + // this.#writeStream('::endgroup::\n'); + // } return this.#onEnd(this); } @@ -252,9 +250,9 @@ export class LogStep { * @param contents Any value that can be converted to a string for writing to `stderr`. */ info(contents: unknown) { - this.#onMessage('info'); + // this.#onMessage('info'); this.hasInfo = true; - this.#writeStream(this.#prefix(prefix.INFO, stringify(contents)), this.verbosity >= 1); + this.#writeStream('info', stringify(contents), this.verbosity >= 1); } /** @@ -274,9 +272,9 @@ export class LogStep { * @param contents Any value that can be converted to a string for writing to `stderr`. */ error(contents: unknown) { - this.#onMessage('error'); + // this.#onMessage('error'); this.hasError = true; - this.#writeStream(this.#prefix(prefix.ERR, stringify(contents)), this.verbosity >= 1); + this.#writeStream('error', stringify(contents), this.verbosity >= 1); } /** @@ -296,9 +294,9 @@ export class LogStep { * @param contents Any value that can be converted to a string for writing to `stderr`. */ warn(contents: unknown) { - this.#onMessage('warn'); + // this.#onMessage('warn'); this.hasWarning = true; - this.#writeStream(this.#prefix(prefix.WARN, stringify(contents)), this.verbosity >= 2); + this.#writeStream('warn', stringify(contents), this.verbosity >= 2); } /** @@ -318,9 +316,9 @@ export class LogStep { * @param contents Any value that can be converted to a string for writing to `stderr`. */ log(contents: unknown) { - this.#onMessage('log'); + // this.#onMessage('log'); this.hasLog = true; - this.#writeStream(this.#prefix(this.name ? prefix.LOG : '', stringify(contents)), this.verbosity >= 3); + this.#writeStream('log', stringify(contents), this.verbosity >= 3); } /** @@ -340,8 +338,8 @@ export class LogStep { * @param contents Any value that can be converted to a string for writing to `stderr`. */ debug(contents: unknown) { - this.#onMessage('debug'); - this.#writeStream(this.#prefix(prefix.DBG, stringify(contents)), this.verbosity >= 4); + // this.#onMessage('debug'); + this.#writeStream('debug', stringify(contents), this.verbosity >= 4); } /** @@ -359,21 +357,19 @@ export class LogStep { this.warn(`Unable to log timing. Missing either mark ${start} → ${end}`); return; } - this.#writeStream( - this.#prefix(prefix.TIMER, `${start} → ${end}: ${Math.round(endMark.startTime - startMark.startTime)}ms`), - ); + this.#writeStream('timing', `${start} → ${end}: ${Math.round(endMark.startTime - startMark.startTime)}ms`); } } - #writeStream(line: string, toBuffer: boolean = true) { + #writeStream(type: LineType, line: string, toBuffer: boolean = true) { if (toBuffer) { this.#buffer.write(ensureNewline(line)); } if (this.#active) { - const lines = line.split('\n'); - const lastThree = lines.slice(-3); - this.#lastThree.push(...lastThree.map(pc.dim)); + // const lines = line.split('\n'); + // const lastThree = lines.slice(-3); + this.#lastThree.push({ type, line }); this.#lastThree.splice(0, this.#lastThree.length - 3); } } diff --git a/modules/logger/src/Logger.ts b/modules/logger/src/Logger.ts index 656971ec..56891df0 100644 --- a/modules/logger/src/Logger.ts +++ b/modules/logger/src/Logger.ts @@ -1,11 +1,16 @@ import type { Writable } from 'node:stream'; +import { cpus } from 'node:os'; +import { EventEmitter } from 'node:events'; import { createLogUpdate } from 'log-update'; import type logUpdate from 'log-update'; import { LogStep } from './LogStep'; import { destroyCurrent, setCurrent } from './global'; +import { LogBuffer, LogBufferToString } from './LogBuffer'; type LogUpdate = typeof logUpdate; +EventEmitter.defaultMaxListeners = cpus().length + 2; + /** * Control the verbosity of the log output * @@ -54,8 +59,8 @@ const frames: Array = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', * @group Logger */ export class Logger { - #defaultLogger: LogStep; - #steps: Array = []; + #defaultLogger: LogBuffer; + #steps: Array = []; #verbosity: Verbosity = 0; #updater: LogUpdate; #frame = 0; @@ -77,22 +82,23 @@ export class Logger { this.verbosity = options.verbosity; this.#stream = options.stream ?? process.stderr; - this.#updater = createLogUpdate(this.#stream); + // this.#updater = createLogUpdate(this.#stream); this.#captureAll = !!options.captureAll; - this.#defaultLogger = new LogStep('', { + this.#defaultLogger = new LogBuffer({ + name: '', onEnd: this.#onEnd, - onMessage: this.#onMessage, - verbosity: this.verbosity, - stream: this.#stream, + // onMessage: this.#onMessage, + // verbosity: this.verbosity, + // stream: this.#stream, }); - if (this.#stream === process.stderr && process.stderr.isTTY && process.env.NODE_ENV !== 'test') { - process.nextTick(() => { - this.#runUpdater(); - }); - } + // if (this.#stream === process.stderr && process.stderr.isTTY && process.env.NODE_ENV !== 'test') { + // process.nextTick(() => { + // this.#runUpdater(); + // }); + // } setCurrent(this); } @@ -118,11 +124,11 @@ export class Logger { this.#verbosity = Math.max(0, value) as Verbosity; if (this.#defaultLogger) { - this.#defaultLogger.verbosity = this.#verbosity; - this.#defaultLogger.activate(true); + // this.#defaultLogger.verbosity = this.#verbosity; + this.#activate(this.#defaultLogger); } - this.#steps.forEach((step) => (step.verbosity = this.#verbosity)); + // this.#steps.forEach((step) => (step.verbosity = this.#verbosity)); } get writable() { @@ -162,8 +168,8 @@ export class Logger { */ set stream(stream: Writable) { this.#stream = stream; - this.#updater.clear(); - this.#updater = createLogUpdate(this.#stream); + // this.#updater.clear(); + // this.#updater = createLogUpdate(this.#stream); } /** @@ -179,7 +185,8 @@ export class Logger { */ pause(write: boolean = true) { this.#paused = true; - clearTimeout(this.#updaterTimeout); + this.#stream.cork(); + // clearTimeout(this.#updaterTimeout); if (write) { this.#writeSteps(); } @@ -189,9 +196,10 @@ export class Logger { * Unpause the logger and resume writing buffered logs to `stderr`. See {@link Logger#pause | `logger.pause()`} for more information. */ unpause() { - this.#updater.clear(); + // this.#updater.clear(); this.#paused = false; - this.#runUpdater(); + this.#stream.uncork(); + // this.#runUpdater(); } #runUpdater() { @@ -210,7 +218,7 @@ export class Logger { return; } this.#updater( - this.#steps.map((step) => [...step.status, ` └ ${frames[this.#frame % frames.length]}`].join('\n')).join('\n'), + this.#steps.map((step) => [` ┌ ${step.name}`, ` └ ${frames[this.#frame % frames.length]}`].join('\n')).join('\n'), ); } @@ -226,14 +234,25 @@ export class Logger { * @param name The name to be written and wrapped around any output logged to this new step. */ createStep(name: string, { writePrefixes }: { writePrefixes?: boolean } = {}) { - const step = new LogStep(name, { + // const step = new LogStep(name, { + // onEnd: this.#onEnd, + // // onMessage: this.#onMessage, + // verbosity: this.verbosity, + // stream: this.#stream, + // writePrefixes, + // }); + + const step = new LogBuffer({ + name, onEnd: this.#onEnd, - onMessage: this.#onMessage, - verbosity: this.verbosity, - stream: this.#stream, - writePrefixes, + // onMessage: this.#onMessage, + // verbosity: this.verbosity, + // stream: this.#stream, + // writePrefixes, }); this.#steps.push(step); + step.on('end', () => this.#onEnd(step)); + this.#activate(step); return step; } @@ -361,41 +380,53 @@ export class Logger { */ async end() { this.pause(false); - clearTimeout(this.#updaterTimeout); - - for (const step of this.#steps) { - this.#activate(step); - step.warn( - 'Step did not finish before command shutdown. Fix this issue by updating this command to `await step.end();` at the appropriate time.', - ); - await step.end(); - } + // clearTimeout(this.#updaterTimeout); + + // for (const step of this.#steps) { + // this.#activate(step); + // step.warn( + // `Step "${step.name}" did not finish before command shutdown. Fix this issue by updating this command to \`await step.end();\` at the appropriate time.`, + // ); + // await step.end(); + // } await this.#defaultLogger.end(); - await this.#defaultLogger.flush(); + // await this.#defaultLogger.flush(); destroyCurrent(); } - #activate(step: LogStep) { - const activeStep = this.#steps.find((step) => step.active); + #activate(step: LogBuffer) { + const activeStep = this.#steps.find((step) => step.isPiped); + if (activeStep) { + // console.log('cannot activate', step.name, 'because', activeStep.name); return; } - if (step !== this.#defaultLogger && this.#defaultLogger.active) { - this.#defaultLogger.deactivate(); + + if (step !== this.#defaultLogger && !this.#defaultLogger.isPaused()) { + this.#defaultLogger.pause(); + // step.unpipe(); + // this.#defaultLogger.deactivate(); } - if (!(this.#stream === process.stderr && process.stderr.isTTY)) { - step.activate(); - return; + if (step.isPiped) { + step.unpipe(); } - setImmediate(() => { - step.activate(); - }); + this.unpause(); + step.pipe(new LogBufferToString({ verbosity: this.#verbosity })).pipe(this.#stream); + step.isPiped = true; + + // if (!(this.#stream === process.stderr && process.stderr.isTTY)) { + // step.pipe(new LogBufferToString({ verbosity: this.#verbosity })).pipe(this.#stream); + // return; + // } + + // setImmediate(() => { + // }); } - #onEnd = async (step: LogStep) => { + #onEnd = async (step: LogBuffer) => { if (step === this.#defaultLogger) { return; } @@ -405,45 +436,64 @@ export class Logger { return; } - this.#updater.clear(); - await step.flush(); + // await new Promise((resolve) => { + // // setImmediate(() => { + // setImmediate(() => { + // resolve(); + // }); + // // }); + // }); - this.#defaultLogger.activate(true); + // this.#updater.clear(); + step.unpipe(); + step.destroy(); + step.isPiped = false; + // step.destroy(); + // await step.flush(); - if (step.hasError && process.env.GITHUB_RUN_ID) { - this.error('The previous step has errors.'); - } + // this.#defaultLogger.resume(); + + // if (step.hasError && process.env.GITHUB_RUN_ID) { + // this.error('The previous step has errors.'); + // } + + // Remove this step + this.#steps.splice(index, 1); await new Promise((resolve) => { setImmediate(() => { setImmediate(() => { - this.#defaultLogger.deactivate(); + this.#defaultLogger.pause(); resolve(); }); }); }); - this.#steps.splice(index, 1); - this.#activate(this.#steps[0] ?? this.#defaultLogger); - }; - - #onMessage = (type: 'error' | 'warn' | 'info' | 'log' | 'debug') => { - switch (type) { - case 'error': - this.#hasError = true; - this.#defaultLogger.hasError = true; - break; - case 'warn': - this.#hasWarning = true; - break; - case 'info': - this.#hasInfo = true; - break; - case 'log': - this.#hasLog = true; - break; - default: - // no default + if (this.#steps.length < 1) { + return; } + + this.#activate(this.#steps[0]); + // this.#steps[0].pipe(new LogBufferToString({ verbosity: this.#verbosity })).pipe(this.#stream); }; + + // #onMessage = (type: 'error' | 'warn' | 'info' | 'log' | 'debug') => { + // switch (type) { + // case 'error': + // this.#hasError = true; + // this.#defaultLogger.hasError = true; + // break; + // case 'warn': + // this.#hasWarning = true; + // break; + // case 'info': + // this.#hasInfo = true; + // break; + // case 'log': + // this.#hasLog = true; + // break; + // default: + // // no default + // } + // }; } diff --git a/modules/logger/src/index.ts b/modules/logger/src/index.ts index d88c65a2..a8f2d77b 100644 --- a/modules/logger/src/index.ts +++ b/modules/logger/src/index.ts @@ -94,17 +94,31 @@ export function bufferSubLogger(step: LogStep): { logger: Logger; end: () => Pro const logger = getLogger(); const buffer = new LogBuffer(); const subLogger = new Logger({ verbosity: logger.verbosity, stream: buffer, captureAll: true }); + let activeStep: Buffer | undefined; + function write(method: 'error' | 'info' | 'warn' | 'log' | 'debug', chunk: Buffer) { + activeStep && step.error(() => activeStep?.toString().trimEnd()); + activeStep = undefined; + step[method](() => chunk.toString().trimEnd()); + } function proxyChunks(chunk: Buffer) { + if (chunk.toString().startsWith(' ┌')) { + activeStep = chunk; + } + + if (chunk.toString().startsWith(' └')) { + activeStep = undefined; + } + if (subLogger.hasError) { - step.error(() => chunk.toString().trimEnd()); + write('error', chunk); } else if (subLogger.hasInfo) { - step.info(() => chunk.toString().trimEnd()); + write('info', chunk); } else if (subLogger.hasWarning) { - step.warn(() => chunk.toString().trimEnd()); + write('warn', chunk); } else if (subLogger.hasLog) { - step.log(() => chunk.toString().trimEnd()); + write('log', chunk); } else { - step.debug(() => chunk.toString().trimEnd()); + write('debug', chunk); } } buffer.on('data', proxyChunks); diff --git a/modules/onerepo/src/core/tasks/__tests__/tasks.test.ts b/modules/onerepo/src/core/tasks/__tests__/tasks.test.ts index 679f8ec5..26c47d3d 100644 --- a/modules/onerepo/src/core/tasks/__tests__/tasks.test.ts +++ b/modules/onerepo/src/core/tasks/__tests__/tasks.test.ts @@ -36,7 +36,7 @@ describe('handler', () => { beforeEach(() => { out = ''; vi.spyOn(process.stdout, 'write').mockImplementation((content) => { - out += content.toString(); + out += (content as string).toString(); return true; }); vi.spyOn(subprocess, 'run').mockResolvedValue(['', '']); diff --git a/modules/onerepo/src/setup/setup.ts b/modules/onerepo/src/setup/setup.ts index 82c3f57c..ae0f8653 100644 --- a/modules/onerepo/src/setup/setup.ts +++ b/modules/onerepo/src/setup/setup.ts @@ -208,6 +208,11 @@ export async function setup({ // Register a new logger on the top of the stack to silence output so that shutdown handlers to not write any output const silencedLogger = new Logger({ verbosity: 0 }); await shutdown(argv); + await new Promise((resolve) => { + setImmediate(() => { + resolve(); + }); + }); await silencedLogger.end(); }, }; From e9c58f2fc46ab279d0bcbfff55b716230a5d9eeb Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Sat, 6 Apr 2024 08:27:08 -0700 Subject: [PATCH 04/17] replace LogStep --- modules/logger/src/LogBuffer.ts | 4 +- modules/logger/src/LogStep.ts | 422 --------------- modules/logger/src/Logger.ts | 16 +- modules/logger/src/__tests__/LogStep.test.ts | 532 +++++++++---------- modules/logger/src/index.ts | 9 +- 5 files changed, 281 insertions(+), 702 deletions(-) delete mode 100644 modules/logger/src/LogStep.ts diff --git a/modules/logger/src/LogBuffer.ts b/modules/logger/src/LogBuffer.ts index bec9fbf4..20186747 100644 --- a/modules/logger/src/LogBuffer.ts +++ b/modules/logger/src/LogBuffer.ts @@ -4,10 +4,10 @@ import pc from 'picocolors'; type BufferOptions = { name: string; description?: string; - onEnd: (step: LogBuffer) => Promise; + onEnd: (step: LogStep) => Promise; }; -export class LogBuffer extends Duplex { +export class LogStep extends Duplex { name?: string; #hasError: boolean = false; #startMark: string; diff --git a/modules/logger/src/LogStep.ts b/modules/logger/src/LogStep.ts deleted file mode 100644 index 78fb5b77..00000000 --- a/modules/logger/src/LogStep.ts +++ /dev/null @@ -1,422 +0,0 @@ -import { performance } from 'node:perf_hooks'; -import type { Writable } from 'node:stream'; -import pc from 'picocolors'; -import type { LineType } from './LogBuffer'; -import { LogBuffer } from './LogBuffer'; - -type StepOptions = { - verbosity: number; - onEnd: (step: LogStep) => Promise; - stream?: Writable; - description?: string; - writePrefixes?: boolean; -}; - -const prefix = { - FAIL: pc.red('✘'), - SUCCESS: pc.green('✔'), - TIMER: pc.red('⏳'), - START: pc.dim(pc.bold('▶︎')), - END: pc.dim(pc.bold('■')), - ERR: pc.red(pc.bold('ERR')), - WARN: pc.yellow(pc.bold('WRN')), - LOG: pc.cyan(pc.bold('LOG')), - DBG: pc.magenta(pc.bold('DBG')), - INFO: pc.blue(pc.bold('INFO')), -}; - -/** - * Log steps should only be created via the {@link Logger#createStep | `logger.createStep()`} method. - * - * ```ts - * const step = logger.createStep('Do some work'); - * // ... long task with a bunch of potential output - * await step.end(); - * ``` - * - * @group Logger - */ -export class LogStep { - #name: string; - #verbosity: number; - #buffer: LogBuffer; - #stream: Writable; - #active = false; - #onEnd: StepOptions['onEnd']; - #lastThree: Array<{ type: LineType; line: string }> = []; - #writing: boolean = false; - #writePrefixes: boolean = true; - #startMark: string; - - /** - * Whether or not an error has been sent to the step. This is not necessarily indicative of uncaught thrown errors, but solely on whether `.error()` has been called in this step. - */ - hasError = false; - /** - * Whether or not a warning has been sent to this step. - */ - hasWarning = false; - /** - * Whether or not an info message has been sent to this step. - */ - hasInfo = false; - /** - * Whether or not a log message has been sent to this step. - */ - hasLog = false; - - /** - * @internal - */ - constructor(name: string, { onEnd, verbosity, stream, description, writePrefixes }: StepOptions) { - this.#startMark = name || `${performance.now()}`; - performance.mark(`onerepo_start_${this.#startMark}`, { - detail: description, - }); - this.#verbosity = verbosity; - this.#name = name; - this.#onEnd = onEnd; - this.#buffer = new LogBuffer({ name }); - this.#stream = stream ?? process.stderr; - this.#writePrefixes = writePrefixes ?? true; - - if (this.name) { - // if (process.env.GITHUB_RUN_ID) { - // this.#writeStream(`::group::${this.name}\n`); - // } - this.#writeStream('start', this.name); - } - } - - /** - * @internal - */ - get writable() { - return this.#stream.writable && this.#buffer.writable; - } - - /** - * @internal - */ - get name() { - return this.#name; - } - - /** - * @internal - */ - get active() { - return this.#active; - } - - /** - * While buffering logs, returns the status line and last 3 lines of buffered output. - * - * @internal - */ - get status(): Array<{ type: LineType; line: string }> { - return this.#lastThree; - } - - /** - * @internal - */ - set verbosity(verbosity: number) { - this.#verbosity = verbosity; - } - - /** - * @internal - */ - get verbosity() { - return this.#verbosity; - } - - /** - * Activate a step. This is typically only called from within the root `Logger` instance and should not be done manually. - * - * @internal - */ - activate(enableWrite = !('isTTY' in this.#stream && this.#stream.isTTY)) { - if (this.#active && this.#writing === enableWrite) { - return; - } - - this.#active = true; - - if (enableWrite) { - this.#enableWrite(); - } - } - - /** - * @internal - */ - deactivate() { - if (!this.#active) { - return; - } - - this.#active = false; - if (this.#writing) { - this.#buffer.cork(); - this.#writing = false; - } - } - - #enableWrite() { - if (this.#writing) { - return; - } - - if (this.verbosity <= 0) { - this.#writing = false; - return; - } - - if (this.#buffer.writableCorked) { - this.#buffer.uncork(); - this.#writing = true; - return; - } - - this.#buffer.pipe(this.#stream); - this.#buffer.read(); - this.#writing = true; - } - - /** - * Finish this step and flush all buffered logs. Once a step is ended, it will no longer accept any logging output and will be effectively removed from the base logger. Consider this method similar to a destructor or teardown. - * - * ```ts - * await step.end(); - * ``` - */ - async end() { - const endMark = performance.mark(`onerepo_end_${this.#startMark}`); - const [startMark] = performance.getEntriesByName(`onerepo_start_${this.#startMark}`); - - // TODO: jest.useFakeTimers does not seem to be applying to performance correctly - const duration = - !startMark || process.env.NODE_ENV === 'test' ? 0 : Math.round(endMark.startTime - startMark.startTime); - const text = this.name - ? pc.dim(`${duration}ms`) - : `Completed${this.hasError ? ' with errors' : ''} ${pc.dim(`${duration}ms`)}`; - this.#writeStream('end', ensureNewline(`${this.hasError ? prefix.FAIL : prefix.SUCCESS} ${text}`)); - // if (this.name && process.env.GITHUB_RUN_ID) { - // this.#writeStream('::endgroup::\n'); - // } - - return this.#onEnd(this); - } - - /** - * @internal - */ - async flush(): Promise { - this.#active = true; - this.#enableWrite(); - - // if not writable, then we can't actually flush/end anything - if (!this.#buffer.writable) { - return; - } - - await new Promise((resolve) => { - setImmediate(() => { - resolve(); - }); - }); - - // Unpipe the buffer, helps with memory/gc - // But do it after a tick (above) otherwise the buffer may not be done flushing to stream - this.#buffer.unpipe(); - } - - /** - * Log an informative message. Should be used when trying to convey information with a user that is important enough to always be returned. - * - * ```ts - * step.info('Log this content when verbosity is >= 1'); - * ``` - * - * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: - * - * ```ts - * step.info(() => bigArray.map((item) => item.name)); - * ``` - * - * @group Logging - * @param contents Any value that can be converted to a string for writing to `stderr`. - */ - info(contents: unknown) { - // this.#onMessage('info'); - this.hasInfo = true; - this.#writeStream('info', stringify(contents), this.verbosity >= 1); - } - - /** - * Log an error. This will cause the root logger to include an error and fail a command. - * - * ```ts - * step.error('Log this content when verbosity is >= 1'); - * ``` - * - * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged error: - * - * ```ts - * step.error(() => bigArray.map((item) => item.name)); - * ``` - * - * @group Logging - * @param contents Any value that can be converted to a string for writing to `stderr`. - */ - error(contents: unknown) { - // this.#onMessage('error'); - this.hasError = true; - this.#writeStream('error', stringify(contents), this.verbosity >= 1); - } - - /** - * Log a warning. Does not have any effect on the command run, but will be called out. - * - * ```ts - * step.warn('Log this content when verbosity is >= 2'); - * ``` - * - * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged warning: - * - * ```ts - * step.warn(() => bigArray.map((item) => item.name)); - * ``` - * - * @group Logging - * @param contents Any value that can be converted to a string for writing to `stderr`. - */ - warn(contents: unknown) { - // this.#onMessage('warn'); - this.hasWarning = true; - this.#writeStream('warn', stringify(contents), this.verbosity >= 2); - } - - /** - * General logging information. Useful for light informative debugging. Recommended to use sparingly. - * - * ```ts - * step.log('Log this content when verbosity is >= 3'); - * ``` - * - * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: - * - * ```ts - * step.log(() => bigArray.map((item) => item.name)); - * ``` - * - * @group Logging - * @param contents Any value that can be converted to a string for writing to `stderr`. - */ - log(contents: unknown) { - // this.#onMessage('log'); - this.hasLog = true; - this.#writeStream('log', stringify(contents), this.verbosity >= 3); - } - - /** - * Extra debug logging when verbosity greater than or equal to 4. - * - * ```ts - * step.debug('Log this content when verbosity is >= 4'); - * ``` - * - * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged debug information: - * - * ```ts - * step.debug(() => bigArray.map((item) => item.name)); - * ``` - * - * @group Logging - * @param contents Any value that can be converted to a string for writing to `stderr`. - */ - debug(contents: unknown) { - // this.#onMessage('debug'); - this.#writeStream('debug', stringify(contents), this.verbosity >= 4); - } - - /** - * Log timing information between two [Node.js performance mark names](https://nodejs.org/dist/latest-v18.x/docs/api/perf_hooks.html#performancemarkname-options). - * - * @group Logging - * @param start A `PerformanceMark` entry name - * @param end A `PerformanceMark` entry name - */ - timing(start: string, end: string) { - if (this.verbosity >= 5) { - const [startMark] = performance.getEntriesByName(start); - const [endMark] = performance.getEntriesByName(end); - if (!startMark || !endMark) { - this.warn(`Unable to log timing. Missing either mark ${start} → ${end}`); - return; - } - this.#writeStream('timing', `${start} → ${end}: ${Math.round(endMark.startTime - startMark.startTime)}ms`); - } - } - - #writeStream(type: LineType, line: string, toBuffer: boolean = true) { - if (toBuffer) { - this.#buffer.write(ensureNewline(line)); - } - - if (this.#active) { - // const lines = line.split('\n'); - // const lastThree = lines.slice(-3); - this.#lastThree.push({ type, line }); - this.#lastThree.splice(0, this.#lastThree.length - 3); - } - } - - #prefixStart(output: string) { - return ` ${this.name ? '┌' : prefix.START} ${output}`; - } - - #prefix(prefix: string, output: string) { - return output - .split('\n') - .map((line) => ` ${this.name ? '│' : ''}${this.#writePrefixes ? ` ${prefix} ` : ''}${line}`) - .join('\n'); - } - - #prefixEnd(output: string) { - return ` ${this.name ? '└' : prefix.END} ${output}`; - } -} - -function stringify(item: unknown): string { - if (typeof item === 'string') { - return item.replace(/^\n+/, '').replace(/\n*$/g, ''); - } - - if ( - Array.isArray(item) || - (typeof item === 'object' && item !== null && item.constructor === Object) || - item === null - ) { - return JSON.stringify(item, null, 2); - } - - if (item instanceof Date) { - return item.toISOString(); - } - - if (typeof item === 'function' && item.length === 0) { - return stringify(item()); - } - - return `${String(item)}`; -} - -function ensureNewline(str: string): string { - if (/^\S*$/.test(str)) { - return ''; - } - return str.endsWith('\n') ? str : str.replace(/\n*$/g, '\n'); -} diff --git a/modules/logger/src/Logger.ts b/modules/logger/src/Logger.ts index 56891df0..d576bb3f 100644 --- a/modules/logger/src/Logger.ts +++ b/modules/logger/src/Logger.ts @@ -3,9 +3,9 @@ import { cpus } from 'node:os'; import { EventEmitter } from 'node:events'; import { createLogUpdate } from 'log-update'; import type logUpdate from 'log-update'; -import { LogStep } from './LogStep'; +// import { LogStep } from './LogStep'; import { destroyCurrent, setCurrent } from './global'; -import { LogBuffer, LogBufferToString } from './LogBuffer'; +import { LogStep, LogBufferToString } from './LogBuffer'; type LogUpdate = typeof logUpdate; @@ -59,8 +59,8 @@ const frames: Array = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', * @group Logger */ export class Logger { - #defaultLogger: LogBuffer; - #steps: Array = []; + #defaultLogger: LogStep; + #steps: Array = []; #verbosity: Verbosity = 0; #updater: LogUpdate; #frame = 0; @@ -86,7 +86,7 @@ export class Logger { this.#captureAll = !!options.captureAll; - this.#defaultLogger = new LogBuffer({ + this.#defaultLogger = new LogStep({ name: '', onEnd: this.#onEnd, // onMessage: this.#onMessage, @@ -242,7 +242,7 @@ export class Logger { // writePrefixes, // }); - const step = new LogBuffer({ + const step = new LogStep({ name, onEnd: this.#onEnd, // onMessage: this.#onMessage, @@ -395,7 +395,7 @@ export class Logger { destroyCurrent(); } - #activate(step: LogBuffer) { + #activate(step: LogStep) { const activeStep = this.#steps.find((step) => step.isPiped); if (activeStep) { @@ -426,7 +426,7 @@ export class Logger { // }); } - #onEnd = async (step: LogBuffer) => { + #onEnd = async (step: LogStep) => { if (step === this.#defaultLogger) { return; } diff --git a/modules/logger/src/__tests__/LogStep.test.ts b/modules/logger/src/__tests__/LogStep.test.ts index 908c3347..8a32caa7 100644 --- a/modules/logger/src/__tests__/LogStep.test.ts +++ b/modules/logger/src/__tests__/LogStep.test.ts @@ -1,268 +1,268 @@ import { PassThrough } from 'node:stream'; import pc from 'picocolors'; -import { LogStep } from '../LogStep'; - -describe('LogStep', () => { - let runId: string | undefined; - - beforeEach(() => { - runId = process.env.GITHUB_RUN_ID; - delete process.env.GITHUB_RUN_ID; - }); - - afterEach(() => { - process.env.GITHUB_RUN_ID = runId; - }); - - test('setup', async () => { - const onEnd = vi.fn(); - const step = new LogStep('tacos', { onEnd, verbosity: 3, onMessage: () => {} }); - - expect(step.name).toBe('tacos'); - expect(step.verbosity).toBe(3); - expect(step.active).toBe(false); - expect(step.status).toEqual([' ┌ tacos']); - }); - - test('can be activated', async () => { - const onEnd = vi.fn(); - const step = new LogStep('tacos', { onEnd, verbosity: 3, onMessage: () => {} }); - step.activate(); - - expect(step.active).toBe(true); - }); - - test('writes group & endgroup when GITHUB_RUN_ID is set', async () => { - process.env.GITHUB_RUN_ID = 'yes'; - const onEnd = vi.fn(() => Promise.resolve()); - const stream = new PassThrough(); - const step = new LogStep('tacos', { onEnd, verbosity: 4, stream, onMessage: () => {} }); - - let out = ''; - stream.on('data', (chunk) => { - out += chunk.toString(); - }); - step.activate(); - - step.log('hello'); - await step.end(); - await step.flush(); - - expect(out).toMatch(/^::group::tacos\n/); - expect(out).toMatch(/::endgroup::\n$/); - }); - - test('when activated, flushes its logs to the stream', async () => { - vi.restoreAllMocks(); - const onEnd = vi.fn(() => Promise.resolve()); - const stream = new PassThrough(); - const step = new LogStep('tacos', { onEnd, verbosity: 3, stream, onMessage: () => {} }); - - let out = ''; - stream.on('data', (chunk) => { - out += chunk.toString(); - }); - - step.log('hellooooo'); - step.activate(); - await step.end(); - await step.flush(); - - expect(out).toEqual( - ` ┌ tacos - │ ${pc.cyan(pc.bold('LOG'))} hellooooo - └ ${pc.green('✔')} ${pc.dim('0ms')} -`, - ); - }); - - test.concurrent.each([ - [0, []], - [1, ['info', 'error']], - [2, ['info', 'error', 'warn']], - [3, ['info', 'error', 'warn', 'log']], - [4, ['info', 'error', 'warn', 'log', 'debug']], - [5, ['info', 'error', 'warn', 'log', 'debug', 'timing']], - ] as Array<[number, Array]>)('verbosity = %d writes %j', async (verbosity, methods) => { - const onEnd = vi.fn(() => Promise.resolve()); - const stream = new PassThrough(); - const step = new LogStep('tacos', { onEnd, verbosity, stream, onMessage: () => {} }); - - const logs = { - info: `${pc.blue(pc.bold('INFO'))} some information`, - error: ` │ ${pc.red(pc.bold('ERR'))} an error`, - warn: ` │ ${pc.yellow(pc.bold('WRN'))} a warning`, - log: ` │ ${pc.cyan(pc.bold('LOG'))} a log`, - debug: ` │ ${pc.magenta(pc.bold('DBG'))} a debug`, - timing: ` │ ${pc.red('⏳')} foo → bar: 0ms`, - }; - - let out = ''; - stream.on('data', (chunk) => { - out += chunk.toString(); - }); - - step.activate(); - - step.info('some information'); - step.error('an error'); - step.warn('a warning'); - step.log('a log'); - step.debug('a debug'); - performance.mark('foo'); - performance.mark('bar'); - step.timing('foo', 'bar'); - - await step.end(); - await step.flush(); - - // Some funky stuff happening here - // @ts-ignore - if (verbosity === 0) { - stream.end(); - } - - for (const [method, str] of Object.entries(logs)) { - // @ts-ignore - if (!methods.includes(method)) { - expect(out).not.toMatch(str); - } else { - expect(out).toMatch(str); - } - } - }); - - test.concurrent.each([ - [ - 'function', - function foo(asdf: unknown) { - return asdf; - }, - ` │ ${pc.cyan(pc.bold('LOG'))} function foo(asdf) {`, - ], - [ - 'function with zero arguments are executed', - function foo() { - return 'tacos'; - }, - ` │ ${pc.cyan(pc.bold('LOG'))} tacos`, - ], - [ - 'object', - { foo: 'bar' }, - ` │ ${pc.cyan(pc.bold('LOG'))} { - │ ${pc.cyan(pc.bold('LOG'))} "foo": "bar" - │ ${pc.cyan(pc.bold('LOG'))} }`, - ], - [ - 'array', - ['foo', true], - ` │ ${pc.cyan(pc.bold('LOG'))} [ - │ ${pc.cyan(pc.bold('LOG'))} "foo", - │ ${pc.cyan(pc.bold('LOG'))} true - │ ${pc.cyan(pc.bold('LOG'))} ]`, - ], - ['date', new Date('2023-03-11'), ` │ ${pc.cyan(pc.bold('LOG'))} 2023-03-11T00:00:00.000Z`], - ])('can stringify %s', async (name, obj, exp) => { - const onEnd = vi.fn(() => Promise.resolve()); - const stream = new PassThrough(); - const step = new LogStep('tacos', { onEnd, verbosity: 3, stream, onMessage: () => {} }); - - let out = ''; - stream.on('data', (chunk) => { - out += chunk.toString(); - }); - - step.log(obj); - step.activate(); - await step.end(); - await step.flush(); - - expect(out).toMatch(exp); - }); - - test('can omit prefixes', async () => { - const onEnd = vi.fn(() => Promise.resolve()); - const stream = new PassThrough(); - const step = new LogStep('tacos', { onEnd, verbosity: 4, stream, writePrefixes: false, onMessage: () => {} }); - - let out = ''; - stream.on('data', (chunk) => { - out += chunk.toString(); - }); - - step.error('error'); - step.warn('warn'); - step.info('info'); - step.log('log'); - step.debug('debug'); - step.activate(); - await step.end(); - await step.flush(); - - expect(out).toEqual(` ┌ tacos - │error - │warn - │info - │log - │debug - └ ${pc.red('✘')} ${pc.dim('0ms')} -`); - }); - - test('sets hasError/etc when messages are added', async () => { - const onEnd = vi.fn(() => Promise.resolve()); - const stream = new PassThrough(); - const step = new LogStep('tacos', { onEnd, verbosity: 4, stream, onMessage: () => {} }); - step.activate(); - - expect(step.hasError).toBe(false); - expect(step.hasWarning).toBe(false); - expect(step.hasInfo).toBe(false); - expect(step.hasLog).toBe(false); - - step.error('foo'); - expect(step.hasError).toBe(true); - - step.warn('foo'); - expect(step.hasWarning).toBe(true); - - step.info('foo'); - expect(step.hasInfo).toBe(true); - - step.log('foo'); - expect(step.hasLog).toBe(true); - - await step.end(); - await step.flush(); - stream.destroy(); - }); - - test('calls onMessage as messages are added', async () => { - const onMessage = vi.fn(); - const stream = new PassThrough(); - const step = new LogStep('tacos', { onEnd: () => Promise.resolve(), verbosity: 4, stream, onMessage }); - step.activate(); - - expect(onMessage).not.toHaveBeenCalled(); - - step.error('foo'); - expect(onMessage).toHaveBeenCalledWith('error'); - - step.warn('foo'); - expect(onMessage).toHaveBeenCalledWith('warn'); - - step.info('foo'); - expect(onMessage).toHaveBeenCalledWith('info'); - - step.log('foo'); - expect(onMessage).toHaveBeenCalledWith('log'); - - step.debug('foo'); - expect(onMessage).toHaveBeenCalledWith('debug'); - - await step.end(); - await step.flush(); - stream.destroy(); - }); -}); +import { LogStep } from '../LogBuffer'; + +// describe('LogStep', () => { +// let runId: string | undefined; + +// beforeEach(() => { +// runId = process.env.GITHUB_RUN_ID; +// delete process.env.GITHUB_RUN_ID; +// }); + +// afterEach(() => { +// process.env.GITHUB_RUN_ID = runId; +// }); + +// test('setup', async () => { +// const onEnd = vi.fn(); +// const step = new LogStep('tacos', { onEnd, verbosity: 3, onMessage: () => {} }); + +// expect(step.name).toBe('tacos'); +// expect(step.verbosity).toBe(3); +// expect(step.active).toBe(false); +// expect(step.status).toEqual([' ┌ tacos']); +// }); + +// test('can be activated', async () => { +// const onEnd = vi.fn(); +// const step = new LogStep('tacos', { onEnd, verbosity: 3, onMessage: () => {} }); +// step.activate(); + +// expect(step.active).toBe(true); +// }); + +// test('writes group & endgroup when GITHUB_RUN_ID is set', async () => { +// process.env.GITHUB_RUN_ID = 'yes'; +// const onEnd = vi.fn(() => Promise.resolve()); +// const stream = new PassThrough(); +// const step = new LogStep('tacos', { onEnd, verbosity: 4, stream, onMessage: () => {} }); + +// let out = ''; +// stream.on('data', (chunk) => { +// out += chunk.toString(); +// }); +// step.activate(); + +// step.log('hello'); +// await step.end(); +// await step.flush(); + +// expect(out).toMatch(/^::group::tacos\n/); +// expect(out).toMatch(/::endgroup::\n$/); +// }); + +// test('when activated, flushes its logs to the stream', async () => { +// vi.restoreAllMocks(); +// const onEnd = vi.fn(() => Promise.resolve()); +// const stream = new PassThrough(); +// const step = new LogStep('tacos', { onEnd, verbosity: 3, stream, onMessage: () => {} }); + +// let out = ''; +// stream.on('data', (chunk) => { +// out += chunk.toString(); +// }); + +// step.log('hellooooo'); +// step.activate(); +// await step.end(); +// await step.flush(); + +// expect(out).toEqual( +// ` ┌ tacos +// │ LOG hellooooo +// └ ✔ 0ms +// `, +// ); +// }); + +// test.concurrent.each([ +// [0, []], +// [1, ['info', 'error']], +// [2, ['info', 'error', 'warn']], +// [3, ['info', 'error', 'warn', 'log']], +// [4, ['info', 'error', 'warn', 'log', 'debug']], +// [5, ['info', 'error', 'warn', 'log', 'debug', 'timing']], +// ] as Array<[number, Array]>)('verbosity = %d writes %j', async (verbosity, methods) => { +// const onEnd = vi.fn(() => Promise.resolve()); +// const stream = new PassThrough(); +// const step = new LogStep('tacos', { onEnd, verbosity, stream, onMessage: () => {} }); + +// const logs = { +// info: `${pc.blue(pc.bold('INFO'))} some information`, +// error: ` │ ${pc.red(pc.bold('ERR'))} an error`, +// warn: ` │ ${pc.yellow(pc.bold('WRN'))} a warning`, +// log: ` │ ${pc.cyan(pc.bold('LOG'))} a log`, +// debug: ` │ ${pc.magenta(pc.bold('DBG'))} a debug`, +// timing: ` │ ${pc.red('⏳')} foo → bar: 0ms`, +// }; + +// let out = ''; +// stream.on('data', (chunk) => { +// out += chunk.toString(); +// }); + +// step.activate(); + +// step.info('some information'); +// step.error('an error'); +// step.warn('a warning'); +// step.log('a log'); +// step.debug('a debug'); +// performance.mark('foo'); +// performance.mark('bar'); +// step.timing('foo', 'bar'); + +// await step.end(); +// await step.flush(); + +// // Some funky stuff happening here +// // @ts-ignore +// if (verbosity === 0) { +// stream.end(); +// } + +// for (const [method, str] of Object.entries(logs)) { +// // @ts-ignore +// if (!methods.includes(method)) { +// expect(out).not.toMatch(str); +// } else { +// expect(out).toMatch(str); +// } +// } +// }); + +// test.concurrent.each([ +// [ +// 'function', +// function foo(asdf: unknown) { +// return asdf; +// }, +// ` │ ${pc.cyan(pc.bold('LOG'))} function foo(asdf) {`, +// ], +// [ +// 'function with zero arguments are executed', +// function foo() { +// return 'tacos'; +// }, +// ` │ ${pc.cyan(pc.bold('LOG'))} tacos`, +// ], +// [ +// 'object', +// { foo: 'bar' }, +// ` │ ${pc.cyan(pc.bold('LOG'))} { +// │ ${pc.cyan(pc.bold('LOG'))} "foo": "bar" +// │ ${pc.cyan(pc.bold('LOG'))} }`, +// ], +// [ +// 'array', +// ['foo', true], +// ` │ ${pc.cyan(pc.bold('LOG'))} [ +// │ ${pc.cyan(pc.bold('LOG'))} "foo", +// │ ${pc.cyan(pc.bold('LOG'))} true +// │ ${pc.cyan(pc.bold('LOG'))} ]`, +// ], +// ['date', new Date('2023-03-11'), ` │ ${pc.cyan(pc.bold('LOG'))} 2023-03-11T00:00:00.000Z`], +// ])('can stringify %s', async (name, obj, exp) => { +// const onEnd = vi.fn(() => Promise.resolve()); +// const stream = new PassThrough(); +// const step = new LogStep('tacos', { onEnd, verbosity: 3, stream, onMessage: () => {} }); + +// let out = ''; +// stream.on('data', (chunk) => { +// out += chunk.toString(); +// }); + +// step.log(obj); +// step.activate(); +// await step.end(); +// await step.flush(); + +// expect(out).toMatch(exp); +// }); + +// test('can omit prefixes', async () => { +// const onEnd = vi.fn(() => Promise.resolve()); +// const stream = new PassThrough(); +// const step = new LogStep('tacos', { onEnd, verbosity: 4, stream, writePrefixes: false, onMessage: () => {} }); + +// let out = ''; +// stream.on('data', (chunk) => { +// out += chunk.toString(); +// }); + +// step.error('error'); +// step.warn('warn'); +// step.info('info'); +// step.log('log'); +// step.debug('debug'); +// step.activate(); +// await step.end(); +// await step.flush(); + +// expect(out).toEqual(` ┌ tacos +// │error +// │warn +// │info +// │log +// │debug +// └ ✘ 0ms +// `); +// }); + +// test('sets hasError/etc when messages are added', async () => { +// const onEnd = vi.fn(() => Promise.resolve()); +// const stream = new PassThrough(); +// const step = new LogStep('tacos', { onEnd, verbosity: 4, stream, onMessage: () => {} }); +// step.activate(); + +// expect(step.hasError).toBe(false); +// expect(step.hasWarning).toBe(false); +// expect(step.hasInfo).toBe(false); +// expect(step.hasLog).toBe(false); + +// step.error('foo'); +// expect(step.hasError).toBe(true); + +// step.warn('foo'); +// expect(step.hasWarning).toBe(true); + +// step.info('foo'); +// expect(step.hasInfo).toBe(true); + +// step.log('foo'); +// expect(step.hasLog).toBe(true); + +// await step.end(); +// await step.flush(); +// stream.destroy(); +// }); + +// test('calls onMessage as messages are added', async () => { +// const onMessage = vi.fn(); +// const stream = new PassThrough(); +// const step = new LogStep('tacos', { onEnd: () => Promise.resolve(), verbosity: 4, stream, onMessage }); +// step.activate(); + +// expect(onMessage).not.toHaveBeenCalled(); + +// step.error('foo'); +// expect(onMessage).toHaveBeenCalledWith('error'); + +// step.warn('foo'); +// expect(onMessage).toHaveBeenCalledWith('warn'); + +// step.info('foo'); +// expect(onMessage).toHaveBeenCalledWith('info'); + +// step.log('foo'); +// expect(onMessage).toHaveBeenCalledWith('log'); + +// step.debug('foo'); +// expect(onMessage).toHaveBeenCalledWith('debug'); + +// await step.end(); +// await step.flush(); +// stream.destroy(); +// }); +// }); diff --git a/modules/logger/src/index.ts b/modules/logger/src/index.ts index a8f2d77b..e959af21 100644 --- a/modules/logger/src/index.ts +++ b/modules/logger/src/index.ts @@ -1,10 +1,11 @@ import { Logger } from './Logger'; -import type { LogStep } from './LogStep'; +// import { LogStep } from './LogStep'; import { destroyCurrent, getCurrent, setCurrent } from './global'; -import { LogBuffer } from './LogBuffer'; +import { LogStep } from './LogBuffer'; export * from './Logger'; -export * from './LogStep'; +// export * from './LogStep'; +export * from './LogBuffer'; /** * This gets the logger singleton for use across all of oneRepo and its commands. @@ -92,7 +93,7 @@ export async function stepWrapper( */ export function bufferSubLogger(step: LogStep): { logger: Logger; end: () => Promise } { const logger = getLogger(); - const buffer = new LogBuffer(); + const buffer = new LogStep({ name: '', onEnd: async () => {} }); const subLogger = new Logger({ verbosity: logger.verbosity, stream: buffer, captureAll: true }); let activeStep: Buffer | undefined; function write(method: 'error' | 'info' | 'warn' | 'log' | 'debug', chunk: Buffer) { From b4e786e9d15b35b6583af40f648b13c4df547f77 Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Sat, 6 Apr 2024 11:23:35 -0700 Subject: [PATCH 05/17] refactor: LogBuffer to LogStep --- .../logger/.changes/000-little-mugs-vanish.md | 7 + modules/logger/package.json | 4 +- modules/logger/src/LogBuffer.ts | 219 ------------------ modules/logger/src/LogStep.ts | 117 ++++++++++ modules/logger/src/Logger.ts | 175 ++++---------- modules/logger/src/__tests__/LogStep.test.ts | 2 +- modules/logger/src/index.ts | 89 +++---- modules/logger/src/transforms/LogProgress.ts | 51 ++++ .../logger/src/transforms/LogStepToString.ts | 76 ++++++ modules/logger/src/utils/cursor.ts | 19 ++ modules/logger/src/utils/string.ts | 30 +++ modules/subprocess/src/index.ts | 61 +++-- yarn.lock | 97 ++------ 13 files changed, 444 insertions(+), 503 deletions(-) create mode 100644 modules/logger/.changes/000-little-mugs-vanish.md delete mode 100644 modules/logger/src/LogBuffer.ts create mode 100644 modules/logger/src/LogStep.ts create mode 100644 modules/logger/src/transforms/LogProgress.ts create mode 100644 modules/logger/src/transforms/LogStepToString.ts create mode 100644 modules/logger/src/utils/cursor.ts create mode 100644 modules/logger/src/utils/string.ts diff --git a/modules/logger/.changes/000-little-mugs-vanish.md b/modules/logger/.changes/000-little-mugs-vanish.md new file mode 100644 index 00000000..c529d28b --- /dev/null +++ b/modules/logger/.changes/000-little-mugs-vanish.md @@ -0,0 +1,7 @@ +--- +type: minor +--- + +The `Logger` and `LogStep` now implement streaming output differently in order to always fully capture potential output and switch on verbosity. + +This is a major internal rewrite, but should be fully compatible with the previous implementations. diff --git a/modules/logger/package.json b/modules/logger/package.json index 59a3c734..e0277689 100644 --- a/modules/logger/package.json +++ b/modules/logger/package.json @@ -21,8 +21,8 @@ "./CHANGELOG.md" ], "dependencies": { - "log-update": "^5.0.1", - "picocolors": "^1.0.0" + "picocolors": "^1.0.0", + "restore-cursor": "^5.0.0" }, "devDependencies": { "@internal/jest-config": "workspace:^", diff --git a/modules/logger/src/LogBuffer.ts b/modules/logger/src/LogBuffer.ts deleted file mode 100644 index 20186747..00000000 --- a/modules/logger/src/LogBuffer.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { Duplex, Transform } from 'node:stream'; -import pc from 'picocolors'; - -type BufferOptions = { - name: string; - description?: string; - onEnd: (step: LogStep) => Promise; -}; - -export class LogStep extends Duplex { - name?: string; - #hasError: boolean = false; - #startMark: string; - #onEnd: BufferOptions['onEnd']; - - isPiped: boolean = false; - - constructor({ description, name, onEnd }: BufferOptions) { - super({ decodeStrings: false }); - - this.#startMark = name || `${performance.now()}`; - performance.mark(`onerepo_start_${this.#startMark}`, { - detail: description, - }); - - this.name = name; - this.#onEnd = onEnd; - this.#write('start', name); - } - - _read() {} - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _write(chunk: string | Buffer, encoding = 'utf8', callback: () => void) { - // this.push( - // typeof chunk === 'string' || chunk instanceof Buffer ? chunk : `${this.#prefix(prefix[chunk.type], chunk.line)}`, - // ); - this.push(chunk); - callback(); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - // _transform(chunk: string | { type: LineType; line: string }, encoding = 'utf8', callback: () => void) { - // // this.push(typeof chunk); - // this.push(typeof chunk === 'string' ? chunk : `${this.#prefix(prefix[chunk.type], chunk.line)}`); - // callback(); - // } - - _final(callback: () => void) { - this.push(null); - callback(); - } - - #write(type: LineType, contents: unknown) { - this.write( - Buffer.from( - JSON.stringify({ - type, - contents: stringify(contents), - group: this.name, - }), - ), - ); - } - - error(contents: unknown) { - this.#hasError = true; - this.#write('error', contents); - } - - warn(contents: unknown) { - this.#write('warn', contents); - } - - info(contents: unknown) { - this.#write('info', contents); - } - - log(contents: unknown) { - this.#write('log', contents); - } - - debug(contents: unknown) { - this.#write('debug', contents); - } - - timing(start: string, end: string) { - // if (this.verbosity >= 5) { - const [startMark] = performance.getEntriesByName(start); - const [endMark] = performance.getEntriesByName(end); - if (!startMark || !endMark) { - this.warn(`Unable to log timing. Missing either mark ${start} → ${end}`); - return; - } - this.#write( - 'timing', - `${startMark.name} → ${endMark.name}: ${Math.round(endMark.startTime - startMark.startTime)}ms`, - ); - // } - } - - // @ts-expect-error - async end() { - const endMark = performance.mark(`onerepo_end_${this.#startMark}`); - const [startMark] = performance.getEntriesByName(`onerepo_start_${this.#startMark}`); - - // TODO: jest.useFakeTimers does not seem to be applying to performance correctly - const duration = - !startMark || process.env.NODE_ENV === 'test' ? 0 : Math.round(endMark.startTime - startMark.startTime); - const contents = this.name - ? pc.dim(`${duration}ms`) - : `Completed${this.#hasError ? ' with errors' : ''} ${pc.dim(`${duration}ms`)}`; - this.#write('end', contents); - this.emit('end'); - this.emit('close'); - } -} - -const prefix: Record = { - // FAIL: pc.red('✘'), - // SUCCESS: pc.green('✔'), - timing: pc.red('⏳'), - start: ' ┌ ', - end: ' └ ', - error: pc.red(pc.bold('ERR ')), - warn: pc.yellow(pc.bold('WRN ')), - log: pc.cyan(pc.bold('LOG ')), - debug: pc.magenta(pc.bold('DBG ')), - info: pc.blue(pc.bold('INFO ')), -}; - -export type LineType = 'start' | 'end' | 'error' | 'warn' | 'info' | 'log' | 'debug' | 'timing'; - -type Options = { - verbosity: number; -}; - -export class LogBufferToString extends Transform { - #verbosity: number; - - constructor({ verbosity }: Options) { - super({ decodeStrings: false }); - this.#verbosity = verbosity; - } - - _transform( - chunk: Buffer, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - encoding = 'utf8', - callback: () => void, - ) { - const data = JSON.parse(chunk.toString()) as { type: LineType; contents: string; group?: string }; - // console.log(chunk.toString()); - if (typeMinVerbosity[data.type] <= this.#verbosity) { - this.push(ensureNewline(`${this.#prefix(data.type, data.group, stringify(data.contents))}`)); - } - callback(); - } - - _final(callback: () => void) { - this.push(null); - callback(); - } - - #prefix(type: LineType, group: string | undefined, output: string) { - if (type === 'end') { - return `${!group ? '◼︎ ' : prefix[type]}${output}`; - } - if (type === 'start') { - return `${!group ? '➤ ' : prefix[type]}${output}`; - } - return output - .split('\n') - .map((line) => ` ${group ? '│ ' : ''}${prefix[type]}${line}`) - .join('\n'); - } -} - -const typeMinVerbosity: Record = { - start: 1, - end: 1, - error: 1, - info: 1, - warn: 2, - log: 3, - debug: 5, - timing: 6, -}; - -function stringify(item: unknown): string { - if (typeof item === 'string') { - return item.replace(/^\n+/, '').replace(/\n*$/g, ''); - } - - if ( - Array.isArray(item) || - (typeof item === 'object' && item !== null && item.constructor === Object) || - item === null - ) { - return JSON.stringify(item, null, 2); - } - - if (item instanceof Date) { - return item.toISOString(); - } - - if (typeof item === 'function' && item.length === 0) { - return stringify(item()); - } - - return `${String(item)}`; -} - -function ensureNewline(str: string): string { - if (/^\S*$/.test(str)) { - return ''; - } - return str.endsWith('\n') ? str : str.replace(/\n*$/g, '\n'); -} diff --git a/modules/logger/src/LogStep.ts b/modules/logger/src/LogStep.ts new file mode 100644 index 00000000..33618d42 --- /dev/null +++ b/modules/logger/src/LogStep.ts @@ -0,0 +1,117 @@ +import { Duplex } from 'node:stream'; +import pc from 'picocolors'; +import { stringify } from './utils/string'; + +export type LogStepOptions = { + name: string; + description?: string; +}; + +export class LogStep extends Duplex { + name?: string; + #hasError: boolean = false; + #startMark: string; + + isPiped: boolean = false; + + constructor({ description, name, onEnd }: LogStepOptions) { + super({ decodeStrings: false }); + + this.#startMark = name || `${performance.now()}`; + performance.mark(`onerepo_start_${this.#startMark}`, { + detail: description, + }); + + this.name = name; + this.#write('start', name); + } + + _read() {} + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _write(chunk: string | Buffer, encoding = 'utf8', callback: () => void) { + this.push(chunk); + callback(); + } + + _final(callback: () => void) { + this.push(null); + callback(); + } + + #write(type: LineType, contents: unknown) { + this.write( + Buffer.from( + JSON.stringify({ + type, + contents: stringify(contents), + group: this.name, + }), + ), + ); + } + + error(contents: unknown) { + this.#hasError = true; + this.#write('error', contents); + } + + warn(contents: unknown) { + this.#write('warn', contents); + } + + info(contents: unknown) { + this.#write('info', contents); + } + + log(contents: unknown) { + this.#write('log', contents); + } + + debug(contents: unknown) { + this.#write('debug', contents); + } + + timing(start: string, end: string) { + const [startMark] = performance.getEntriesByName(start); + const [endMark] = performance.getEntriesByName(end); + if (!startMark || !endMark) { + this.warn(`Unable to log timing. Missing either mark ${start} → ${end}`); + return; + } + this.#write( + 'timing', + `${startMark.name} → ${endMark.name}: ${Math.round(endMark.startTime - startMark.startTime)}ms`, + ); + } + + end() { + // Makes calling `.end()` multiple times safe. + // TODO: make this unnecessary + if (this.writableEnded) { + throw new Error(`Called step.end() multiple times on step "${this.name}"`); + } + + const endMark = performance.mark(`onerepo_end_${this.#startMark}`); + const [startMark] = performance.getEntriesByName(`onerepo_start_${this.#startMark}`); + + // TODO: jest.useFakeTimers does not seem to be applying to performance correctly + const duration = + !startMark || process.env.NODE_ENV === 'test' ? 0 : Math.round(endMark.startTime - startMark.startTime); + const contents = this.name + ? pc.dim(`${duration}ms`) + : `Completed${this.#hasError ? ' with errors' : ''} ${pc.dim(`${duration}ms`)}`; + + return super.end( + Buffer.from( + JSON.stringify({ + type: 'end', + contents: stringify(contents), + group: this.name, + }), + ), + ); + } +} + +export type LineType = 'start' | 'end' | 'error' | 'warn' | 'info' | 'log' | 'debug' | 'timing'; diff --git a/modules/logger/src/Logger.ts b/modules/logger/src/Logger.ts index d576bb3f..92d3ca77 100644 --- a/modules/logger/src/Logger.ts +++ b/modules/logger/src/Logger.ts @@ -1,15 +1,11 @@ import type { Writable } from 'node:stream'; -import { cpus } from 'node:os'; -import { EventEmitter } from 'node:events'; -import { createLogUpdate } from 'log-update'; -import type logUpdate from 'log-update'; -// import { LogStep } from './LogStep'; import { destroyCurrent, setCurrent } from './global'; -import { LogStep, LogBufferToString } from './LogBuffer'; +import { LogStep } from './LogStep'; +import { LogStepToString } from './transforms/LogStepToString'; +import { LogProgress } from './transforms/LogProgress'; +import { hideCursor, showCursor } from './utils/cursor'; -type LogUpdate = typeof logUpdate; - -EventEmitter.defaultMaxListeners = cpus().length + 2; +// EventEmitter.defaultMaxListeners = cpus().length + 2; /** * Control the verbosity of the log output @@ -38,15 +34,13 @@ export type LoggerOptions = { /** * Advanced – override the writable stream in order to pipe logs elsewhere. Mostly used for dependency injection for `@onerepo/test-cli`. */ - stream?: Writable; + stream?: Writable | LogStep; /** * @experimental */ captureAll?: boolean; }; -const frames: Array = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; - /** * The oneRepo logger helps build commands and capture output from spawned subprocess in a way that's both delightful to the end user and includes easy to scan and follow output. * @@ -62,12 +56,7 @@ export class Logger { #defaultLogger: LogStep; #steps: Array = []; #verbosity: Verbosity = 0; - #updater: LogUpdate; - #frame = 0; - #stream: Writable; - - #paused = false; - #updaterTimeout: NodeJS.Timeout | undefined; + #stream: Writable | LogStep; #hasError = false; #hasWarning = false; @@ -80,25 +69,9 @@ export class Logger { */ constructor(options: LoggerOptions) { this.verbosity = options.verbosity; - this.#stream = options.stream ?? process.stderr; - // this.#updater = createLogUpdate(this.#stream); - this.#captureAll = !!options.captureAll; - - this.#defaultLogger = new LogStep({ - name: '', - onEnd: this.#onEnd, - // onMessage: this.#onMessage, - // verbosity: this.verbosity, - // stream: this.#stream, - }); - - // if (this.#stream === process.stderr && process.stderr.isTTY && process.env.NODE_ENV !== 'test') { - // process.nextTick(() => { - // this.#runUpdater(); - // }); - // } + this.#defaultLogger = new LogStep({ name: '' }); setCurrent(this); } @@ -166,10 +139,15 @@ export class Logger { /** * @internal */ - set stream(stream: Writable) { + set stream(stream: Writable | LogStep) { this.#stream = stream; - // this.#updater.clear(); - // this.#updater = createLogUpdate(this.#stream); + } + + /** + * @internal + */ + get stream() { + return this.#stream; } /** @@ -183,43 +161,17 @@ export class Logger { * logger.unpause(); * ``` */ - pause(write: boolean = true) { - this.#paused = true; + pause() { this.#stream.cork(); - // clearTimeout(this.#updaterTimeout); - if (write) { - this.#writeSteps(); - } + showCursor(); } /** * Unpause the logger and resume writing buffered logs to `stderr`. See {@link Logger#pause | `logger.pause()`} for more information. */ unpause() { - // this.#updater.clear(); - this.#paused = false; this.#stream.uncork(); - // this.#runUpdater(); - } - - #runUpdater() { - if (this.#paused) { - return; - } - this.#updaterTimeout = setTimeout(() => { - this.#writeSteps(); - this.#frame += 1; - this.#runUpdater(); - }, 80); - } - - #writeSteps() { - if (process.env.NODE_ENV === 'test' || this.verbosity <= 0) { - return; - } - this.#updater( - this.#steps.map((step) => [` ┌ ${step.name}`, ` └ ${frames[this.#frame % frames.length]}`].join('\n')).join('\n'), - ); + hideCursor(); } /** @@ -234,22 +186,7 @@ export class Logger { * @param name The name to be written and wrapped around any output logged to this new step. */ createStep(name: string, { writePrefixes }: { writePrefixes?: boolean } = {}) { - // const step = new LogStep(name, { - // onEnd: this.#onEnd, - // // onMessage: this.#onMessage, - // verbosity: this.verbosity, - // stream: this.#stream, - // writePrefixes, - // }); - - const step = new LogStep({ - name, - onEnd: this.#onEnd, - // onMessage: this.#onMessage, - // verbosity: this.verbosity, - // stream: this.#stream, - // writePrefixes, - }); + const step = new LogStep({ name }); this.#steps.push(step); step.on('end', () => this.#onEnd(step)); @@ -379,20 +316,19 @@ export class Logger { * @internal */ async end() { - this.pause(false); - // clearTimeout(this.#updaterTimeout); - - // for (const step of this.#steps) { - // this.#activate(step); - // step.warn( - // `Step "${step.name}" did not finish before command shutdown. Fix this issue by updating this command to \`await step.end();\` at the appropriate time.`, - // ); - // await step.end(); - // } + this.pause(); + + for (const step of this.#steps) { + this.#activate(step); + step.warn( + `Step "${step.name}" did not finish before command shutdown. Fix this issue by updating this command to \`await step.end();\` at the appropriate time.`, + ); + step.end(); + } await this.#defaultLogger.end(); - // await this.#defaultLogger.flush(); destroyCurrent(); + showCursor(); } #activate(step: LogStep) { @@ -405,29 +341,28 @@ export class Logger { if (step !== this.#defaultLogger && !this.#defaultLogger.isPaused()) { this.#defaultLogger.pause(); - // step.unpipe(); - // this.#defaultLogger.deactivate(); } if (step.isPiped) { - step.unpipe(); + return; + // step.unpipe(); } this.unpause(); - step.pipe(new LogBufferToString({ verbosity: this.#verbosity })).pipe(this.#stream); - step.isPiped = true; - // if (!(this.#stream === process.stderr && process.stderr.isTTY)) { - // step.pipe(new LogBufferToString({ verbosity: this.#verbosity })).pipe(this.#stream); - // return; - // } - - // setImmediate(() => { - // }); + if (!step.name) { + step.pipe(new LogStepToString({ verbosity: this.#verbosity })).pipe(this.#stream); + } else { + step + .pipe(new LogStepToString({ verbosity: this.#verbosity })) + .pipe(new LogProgress()) + .pipe(this.#stream); + } + step.isPiped = true; } #onEnd = async (step: LogStep) => { - if (step === this.#defaultLogger) { + if (step === this.#defaultLogger || !step.isPiped) { return; } @@ -436,15 +371,6 @@ export class Logger { return; } - // await new Promise((resolve) => { - // // setImmediate(() => { - // setImmediate(() => { - // resolve(); - // }); - // // }); - // }); - - // this.#updater.clear(); step.unpipe(); step.destroy(); step.isPiped = false; @@ -460,21 +386,20 @@ export class Logger { // Remove this step this.#steps.splice(index, 1); - await new Promise((resolve) => { - setImmediate(() => { - setImmediate(() => { - this.#defaultLogger.pause(); - resolve(); - }); - }); - }); + // await new Promise((resolve) => { + // setImmediate(() => { + // setImmediate(() => { + this.#defaultLogger.pause(); + // resolve(); + // }); + // }); + // }); if (this.#steps.length < 1) { return; } this.#activate(this.#steps[0]); - // this.#steps[0].pipe(new LogBufferToString({ verbosity: this.#verbosity })).pipe(this.#stream); }; // #onMessage = (type: 'error' | 'warn' | 'info' | 'log' | 'debug') => { diff --git a/modules/logger/src/__tests__/LogStep.test.ts b/modules/logger/src/__tests__/LogStep.test.ts index 8a32caa7..8835a5f6 100644 --- a/modules/logger/src/__tests__/LogStep.test.ts +++ b/modules/logger/src/__tests__/LogStep.test.ts @@ -1,6 +1,6 @@ import { PassThrough } from 'node:stream'; import pc from 'picocolors'; -import { LogStep } from '../LogBuffer'; +import { LogStep } from '../LogStep'; // describe('LogStep', () => { // let runId: string | undefined; diff --git a/modules/logger/src/index.ts b/modules/logger/src/index.ts index e959af21..cb740251 100644 --- a/modules/logger/src/index.ts +++ b/modules/logger/src/index.ts @@ -1,11 +1,13 @@ +import { PassThrough } from 'node:stream'; +import restoreCursorDefault from 'restore-cursor'; import { Logger } from './Logger'; // import { LogStep } from './LogStep'; import { destroyCurrent, getCurrent, setCurrent } from './global'; -import { LogStep } from './LogBuffer'; +import type { LogStep } from './LogStep'; export * from './Logger'; // export * from './LogStep'; -export * from './LogBuffer'; +export * from './LogStep'; /** * This gets the logger singleton for use across all of oneRepo and its commands. @@ -40,6 +42,7 @@ export function getLogger(opts: Partial[0]> */ export function destroyLogger() { destroyCurrent(); + restoreCursor(); } /** @@ -93,48 +96,54 @@ export async function stepWrapper( */ export function bufferSubLogger(step: LogStep): { logger: Logger; end: () => Promise } { const logger = getLogger(); - const buffer = new LogStep({ name: '', onEnd: async () => {} }); - const subLogger = new Logger({ verbosity: logger.verbosity, stream: buffer, captureAll: true }); - let activeStep: Buffer | undefined; - function write(method: 'error' | 'info' | 'warn' | 'log' | 'debug', chunk: Buffer) { - activeStep && step.error(() => activeStep?.toString().trimEnd()); - activeStep = undefined; - step[method](() => chunk.toString().trimEnd()); - } - function proxyChunks(chunk: Buffer) { - if (chunk.toString().startsWith(' ┌')) { - activeStep = chunk; - } - - if (chunk.toString().startsWith(' └')) { - activeStep = undefined; - } - - if (subLogger.hasError) { - write('error', chunk); - } else if (subLogger.hasInfo) { - write('info', chunk); - } else if (subLogger.hasWarning) { - write('warn', chunk); - } else if (subLogger.hasLog) { - write('log', chunk); - } else { - write('debug', chunk); - } - } - buffer.on('data', proxyChunks); + // const buffer = new LogStep({ name: '' }); + const stream = new PassThrough(); + const subLogger = new Logger({ verbosity: logger.verbosity, stream, captureAll: true }); + // let activeStep: Buffer | undefined; + // function write(method: 'error' | 'info' | 'warn' | 'log' | 'debug', chunk: Buffer) { + // activeStep && step.error(() => activeStep?.toString().trimEnd()); + // activeStep = undefined; + // step[method](() => chunk.toString().trimEnd()); + // } + // function proxyChunks(chunk: Buffer) { + // if (chunk.toString().startsWith(' ┌')) { + // activeStep = chunk; + // } + + // if (chunk.toString().startsWith(' └')) { + // activeStep = undefined; + // } + + // if (subLogger.hasError) { + // write('error', chunk); + // } else if (subLogger.hasInfo) { + // write('info', chunk); + // } else if (subLogger.hasWarning) { + // write('warn', chunk); + // } else if (subLogger.hasLog) { + // write('log', chunk); + // } else { + // write('debug', chunk); + // } + // } + + stream.pipe(step); + // buffer.on('data', proxyChunks); return { logger: subLogger, async end() { - buffer.off('data', proxyChunks); - await new Promise((resolve) => { - setImmediate(async () => { - await subLogger.end(); - buffer.destroy(); - resolve(); - }); - }); + // buffer.unpipe(); + // buffer.off('data', proxyChunks); + // await new Promise((resolve) => { + // setImmediate(async () => { + // await subLogger.end(); + // buffer.destroy(); + // resolve(); + // }); + // }); }, }; } + +export const restoreCursor = restoreCursorDefault; diff --git a/modules/logger/src/transforms/LogProgress.ts b/modules/logger/src/transforms/LogProgress.ts new file mode 100644 index 00000000..31dabc21 --- /dev/null +++ b/modules/logger/src/transforms/LogProgress.ts @@ -0,0 +1,51 @@ +import { Transform } from 'node:stream'; + +export class LogProgress extends Transform { + #updaterTimeout?: NodeJS.Timeout; + #frame: number; + #written: boolean = false; + + constructor() { + super(); + // this.#updater = createLogUpdate(this); + this.#frame = 0; + this.#runUpdater(); + } + + #runUpdater() { + clearTimeout(this.#updaterTimeout); + this.#updaterTimeout = setTimeout(() => { + // this.push(new Error().stack); + this.write(` └ ${frames[this.#frame % frames.length]}`); + this.#written = true; + this.#frame += 1; + // this.#runUpdater(); + }, 100); + } + + _destroy() { + clearTimeout(this.#updaterTimeout); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _transform(chunk: string | Buffer, encoding = 'utf8', callback: () => void) { + clearTimeout(this.#updaterTimeout); + if (this.#written) { + // Erase the last line + this.push('\u001B[2K\u001B[G'); + this.#written = false; + } + + this.push(chunk); + callback(); + this.#runUpdater(); + } + + _final(callback: () => void) { + clearTimeout(this.#updaterTimeout); + this.push(null); + callback(); + } +} + +export const frames: Array = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; diff --git a/modules/logger/src/transforms/LogStepToString.ts b/modules/logger/src/transforms/LogStepToString.ts new file mode 100644 index 00000000..53a373ce --- /dev/null +++ b/modules/logger/src/transforms/LogStepToString.ts @@ -0,0 +1,76 @@ +import { Transform } from 'node:stream'; +import pc from 'picocolors'; +import type { LineType } from '../LogStep'; +import { ensureNewline, stringify } from '../utils/string'; + +export type StepToStringOptions = { + verbosity: number; +}; + +export class LogStepToString extends Transform { + #verbosity: number; + + constructor({ verbosity }: StepToStringOptions) { + super({ decodeStrings: false }); + this.#verbosity = verbosity; + } + + _transform( + chunk: Buffer, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + encoding = 'utf8', + callback: () => void, + ) { + try { + const data = JSON.parse(chunk.toString()) as { type: LineType; contents: string; group?: string }; + // console.log(chunk.toString()); + if (typeMinVerbosity[data.type] <= this.#verbosity) { + this.push(ensureNewline(`${this.#prefix(data.type, data.group, stringify(data.contents))}`)); + } + } catch (e) { + this.push(chunk); + } + callback(); + } + + _final(callback: () => void) { + this.push(null); + callback(); + } + + #prefix(type: LineType, group: string | undefined, output: string) { + if (type === 'end') { + return `${!group ? '◼︎ ' : prefix[type]}${output}`; + } + if (type === 'start') { + return `${!group ? '➤ ' : prefix[type]}${output}`; + } + return output + .split('\n') + .map((line) => ` ${group ? '│ ' : ''}${prefix[type]}${line}`) + .join('\n'); + } +} +const typeMinVerbosity: Record = { + start: 1, + end: 1, + error: 1, + info: 1, + warn: 2, + log: 3, + debug: 5, + timing: 6, +}; + +export const prefix: Record = { + // FAIL: pc.red('✘'), + // SUCCESS: pc.green('✔'), + timing: pc.red('⏳'), + start: ' ┌ ', + end: ' └ ', + error: pc.red(pc.bold('ERR ')), + warn: pc.yellow(pc.bold('WRN ')), + log: pc.cyan(pc.bold('LOG ')), + debug: pc.magenta(pc.bold('DBG ')), + info: pc.blue(pc.bold('INFO ')), +}; diff --git a/modules/logger/src/utils/cursor.ts b/modules/logger/src/utils/cursor.ts new file mode 100644 index 00000000..266ea25f --- /dev/null +++ b/modules/logger/src/utils/cursor.ts @@ -0,0 +1,19 @@ +import restoreCursor from 'restore-cursor'; + +export function hideCursor() { + if (!process.stderr.isTTY) { + return; + } + + restoreCursor(); + + process.stderr.write('\u001B[?25l'); +} + +export function showCursor() { + if (!process.stderr.isTTY) { + return; + } + + process.stderr.write('\u001B[?25h'); +} diff --git a/modules/logger/src/utils/string.ts b/modules/logger/src/utils/string.ts new file mode 100644 index 00000000..3b4c3186 --- /dev/null +++ b/modules/logger/src/utils/string.ts @@ -0,0 +1,30 @@ +export function stringify(item: unknown): string { + if (typeof item === 'string') { + return item.replace(/^\n+/, '').replace(/\n*$/g, ''); + } + + if ( + Array.isArray(item) || + (typeof item === 'object' && item !== null && item.constructor === Object) || + item === null + ) { + return JSON.stringify(item, null, 2); + } + + if (item instanceof Date) { + return item.toISOString(); + } + + if (typeof item === 'function' && item.length === 0) { + return stringify(item()); + } + + return `${String(item)}`; +} + +export function ensureNewline(str: string): string { + if (/^\S*$/.test(str)) { + return ''; + } + return str.endsWith('\n') ? str : str.replace(/\n*$/g, '\n'); +} diff --git a/modules/subprocess/src/index.ts b/modules/subprocess/src/index.ts index bffc3a5f..18b96399 100644 --- a/modules/subprocess/src/index.ts +++ b/modules/subprocess/src/index.ts @@ -132,13 +132,11 @@ export async function run(options: RunSpec): Promise<[string, string]> { ${JSON.stringify(withoutLogger, null, 2)}\n`, ); - return Promise.resolve() - .then(() => { - return !inputStep ? step.end() : Promise.resolve(); - }) - .then(() => { - resolve([out.trim(), err.trim()]); - }); + if (!inputStep) { + step.end(); + } + + return resolve([out.trim(), err.trim()]); } if (inputStep) { @@ -160,17 +158,12 @@ ${JSON.stringify(withoutLogger, null, 2)}\n${process.env.ONEREPO_ROOT ?? process }); subprocess.on('error', (error) => { - if (!options.skipFailures) { - step.error(error); + !options.skipFailures && step.error(error); + logger.unpause(); + if (!inputStep) { + step.end(); } - return Promise.resolve() - .then(() => { - logger.unpause(); - return !inputStep ? step.end() : Promise.resolve(); - }) - .then(() => { - reject(error); - }); + return reject(error); }); if (subprocess.stdout && subprocess.stderr) { @@ -202,23 +195,24 @@ ${JSON.stringify(withoutLogger, null, 2)}\n${process.env.ONEREPO_ROOT ?? process const error = new SubprocessError(`${sortedOut || code}`); step.error(sortedOut.trim()); step.error(`Process exited with code ${code}`); - return (!inputStep ? step.end() : Promise.resolve()).then(() => { - logger.unpause(); - reject(error); - }); + if (!inputStep) { + step.end(); + } + + logger.unpause(); + reject(error); + return; } if (inputStep) { step.timing(`onerepo_start_Subprocess: ${options.name}`, `onerepo_end_Subprocess: ${options.name}`); } - return Promise.resolve() - .then(() => { - return !inputStep ? step.end() : Promise.resolve(); - }) - .then(() => { - resolve([out.trim(), err.trim()]); - }); + if (!inputStep) { + step.end(); + } + + return resolve([out.trim(), err.trim()]); }); }); } @@ -286,7 +280,7 @@ export async function sudo(options: Omit & { reason?: string }) return new Promise((resolve, reject) => { try { execSync('sudo -n true &> /dev/null'); - } catch { + } catch (e) { step.warn('Sudo permissions are required to continue!'); step.warn(options.reason ?? 'If prompted, please type your password and hit [RETURN].'); step.debug(`Sudo permissions are being requested to run the following: @@ -311,12 +305,9 @@ export async function sudo(options: Omit & { reason?: string }) logger.log(stdout); logger.log(stderr); - return Promise.resolve() - .then(() => { - logger.unpause(); - return step.end(); - }) - .then(() => resolve([stdout, stderr])); + logger.unpause(); + step.end(); + resolve([stdout, stderr]); }, ); }); diff --git a/yarn.lock b/yarn.lock index ac155bea..2af41bff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2132,8 +2132,8 @@ __metadata: "@internal/jest-config": "workspace:^" "@internal/tsconfig": "workspace:^" "@internal/vitest-config": "workspace:^" - log-update: ^5.0.1 picocolors: ^1.0.0 + restore-cursor: ^5.0.0 typescript: ^5.7.2 languageName: unknown linkType: soft @@ -3804,15 +3804,6 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^5.0.0": - version: 5.0.0 - resolution: "ansi-escapes@npm:5.0.0" - dependencies: - type-fest: ^1.0.2 - checksum: d4b5eb8207df38367945f5dd2ef41e08c28edc192dc766ef18af6b53736682f49d8bfcfa4e4d6ecbc2e2f97c258fda084fb29a9e43b69170b71090f771afccac - languageName: node - linkType: hard - "ansi-regex@npm:^5.0.1": version: 5.0.1 resolution: "ansi-regex@npm:5.0.1" @@ -3843,7 +3834,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": +"ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": version: 6.2.1 resolution: "ansi-styles@npm:6.2.1" checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 @@ -4841,15 +4832,6 @@ __metadata: languageName: node linkType: hard -"cli-cursor@npm:^4.0.0": - version: 4.0.0 - resolution: "cli-cursor@npm:4.0.0" - dependencies: - restore-cursor: ^4.0.0 - checksum: ab3f3ea2076e2176a1da29f9d64f72ec3efad51c0960898b56c8a17671365c26e67b735920530eaf7328d61f8bd41c27f46b9cf6e4e10fe2fa44b5e8c0e392cc - languageName: node - linkType: hard - "cli-cursor@npm:^5.0.0": version: 5.0.0 resolution: "cli-cursor@npm:5.0.0" @@ -8409,13 +8391,6 @@ __metadata: languageName: node linkType: hard -"is-fullwidth-code-point@npm:^4.0.0": - version: 4.0.0 - resolution: "is-fullwidth-code-point@npm:4.0.0" - checksum: 8ae89bf5057bdf4f57b346fb6c55e9c3dd2549983d54191d722d5c739397a903012cc41a04ee3403fd872e811243ef91a7c5196da7b5841dc6b6aae31a264a8d - languageName: node - linkType: hard - "is-generator-fn@npm:^2.0.0": version: 2.1.0 resolution: "is-generator-fn@npm:2.1.0" @@ -9683,19 +9658,6 @@ __metadata: languageName: node linkType: hard -"log-update@npm:^5.0.1": - version: 5.0.1 - resolution: "log-update@npm:5.0.1" - dependencies: - ansi-escapes: ^5.0.0 - cli-cursor: ^4.0.0 - slice-ansi: ^5.0.0 - strip-ansi: ^7.0.1 - wrap-ansi: ^8.0.1 - checksum: 2c6b47dcce6f9233df6d232a37d9834cb3657a0749ef6398f1706118de74c55f158587d4128c225297ea66803f35c5ac3db4f3f617046d817233c45eedc32ef1 - languageName: node - linkType: hard - "longest-streak@npm:^3.0.0": version: 3.1.0 resolution: "longest-streak@npm:3.1.0" @@ -10726,10 +10688,10 @@ __metadata: languageName: node linkType: hard -"mimic-function@npm:^5.0.0": - version: 5.0.1 - resolution: "mimic-function@npm:5.0.1" - checksum: eb5893c99e902ccebbc267c6c6b83092966af84682957f79313311edb95e8bb5f39fb048d77132b700474d1c86d90ccc211e99bae0935447a4834eb4c882982c +"mimic-fn@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-fn@npm:4.0.0" + checksum: 995dcece15ee29aa16e188de6633d43a3db4611bcf93620e7e62109ec41c79c0f34277165b8ce5e361205049766e371851264c21ac64ca35499acb5421c2ba56 languageName: node linkType: hard @@ -11273,12 +11235,12 @@ __metadata: languageName: node linkType: hard -"onetime@npm:^7.0.0": - version: 7.0.0 - resolution: "onetime@npm:7.0.0" +"onetime@npm:^6.0.0": + version: 6.0.0 + resolution: "onetime@npm:6.0.0" dependencies: - mimic-function: ^5.0.0 - checksum: eb08d2da9339819e2f9d52cab9caf2557d80e9af8c7d1ae86e1a0fef027d00a88e9f5bd67494d350df360f7c559fbb44e800b32f310fb989c860214eacbb561c + mimic-fn: ^4.0.0 + checksum: 0846ce78e440841335d4e9182ef69d5762e9f38aa7499b19f42ea1c4cd40f0b4446094c455c713f9adac3f4ae86f613bb5e30c99e52652764d06a89f709b3788 languageName: node linkType: hard @@ -12425,23 +12387,13 @@ __metadata: languageName: node linkType: hard -"restore-cursor@npm:^4.0.0": - version: 4.0.0 - resolution: "restore-cursor@npm:4.0.0" - dependencies: - onetime: ^5.1.0 - signal-exit: ^3.0.2 - checksum: 5b675c5a59763bf26e604289eab35711525f11388d77f409453904e1e69c0d37ae5889295706b2c81d23bd780165084d040f9b68fffc32cc921519031c4fa4af - languageName: node - linkType: hard - "restore-cursor@npm:^5.0.0": - version: 5.1.0 - resolution: "restore-cursor@npm:5.1.0" + version: 5.0.0 + resolution: "restore-cursor@npm:5.0.0" dependencies: - onetime: ^7.0.0 + onetime: ^6.0.0 signal-exit: ^4.1.0 - checksum: 838dd54e458d89cfbc1a923b343c1b0f170a04100b4ce1733e97531842d7b440463967e521216e8ab6c6f8e89df877acc7b7f4c18ec76e99fb9bf5a60d358d2c + checksum: e7c29a44105e877cf1836dd9901261519941caa3db2c7340d41a3cd1e6527a4522831dd1dc205eb638a026e6033940fb6b07d9326f8890d1bf0acdce41858786 languageName: node linkType: hard @@ -13012,16 +12964,6 @@ __metadata: languageName: node linkType: hard -"slice-ansi@npm:^5.0.0": - version: 5.0.0 - resolution: "slice-ansi@npm:5.0.0" - dependencies: - ansi-styles: ^6.0.0 - is-fullwidth-code-point: ^4.0.0 - checksum: 7e600a2a55e333a21ef5214b987c8358fe28bfb03c2867ff2cbf919d62143d1812ac27b4297a077fdaf27a03da3678e49551c93e35f9498a3d90221908a1180e - languageName: node - linkType: hard - "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -13778,13 +13720,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^1.0.2": - version: 1.4.0 - resolution: "type-fest@npm:1.4.0" - checksum: b011c3388665b097ae6a109a437a04d6f61d81b7357f74cbcb02246f2f5bd72b888ae33631b99871388122ba0a87f4ff1c94078e7119ff22c70e52c0ff828201 - languageName: node - linkType: hard - "type-fest@npm:^4.21.0": version: 4.30.0 resolution: "type-fest@npm:4.30.0" @@ -14798,7 +14733,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^8.0.1, wrap-ansi@npm:^8.1.0": +"wrap-ansi@npm:^8.1.0": version: 8.1.0 resolution: "wrap-ansi@npm:8.1.0" dependencies: From 2faaa6964ead149fe3f1316a94880642ff01815a Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Sun, 7 Apr 2024 09:11:47 -0700 Subject: [PATCH 06/17] fix: various verbosity issues: --- docs/src/content/docs/api/index.md | 5106 ++++++++++++++++- docs/src/content/docs/plugins/docgen.mdx | 102 +- modules/logger/src/LogStep.ts | 44 +- modules/logger/src/Logger.ts | 138 +- modules/logger/src/__tests__/Logger.test.ts | 12 +- modules/logger/src/index.ts | 36 +- .../logger/src/transforms/LogStepToString.ts | 26 +- modules/logger/src/types.ts | 25 + .../src/core/graph/__tests__/verify.test.ts | 2 +- modules/onerepo/src/core/graph/verify.ts | 11 +- modules/onerepo/src/setup/setup.ts | 16 +- modules/test-cli/src/index.ts | 17 +- 12 files changed, 5125 insertions(+), 410 deletions(-) create mode 100644 modules/logger/src/types.ts diff --git a/docs/src/content/docs/api/index.md b/docs/src/content/docs/api/index.md index 99030a7f..ce71725b 100644 --- a/docs/src/content/docs/api/index.md +++ b/docs/src/content/docs/api/index.md @@ -4,7 +4,4826 @@ description: Full API documentation for oneRepo. --- - + + +## Classes + +### LogStep + +#### Extends + +- `Duplex` + +#### Constructors + +##### new LogStep() + +```ts +new LogStep(__namedParameters): LogStep +``` + +**Parameters:** + +| Parameter | Type | +| ------------------- | ----------------------------------- | +| `__namedParameters` | [`LogStepOptions`](#logstepoptions) | + +**Returns:** [`LogStep`](#logstep) + +###### Overrides + +`Duplex.constructor` + +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +#### Properties + +| Property | Modifier | Type | Description | Inherited from | Defined in | +| ------------------------ | ---------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `allowHalfOpen` | `public` | `boolean` | If `false` then the stream will automatically end the writable side when the readable side ends. Set initially by the `allowHalfOpen` constructor option, which defaults to `true`. This can be changed manually to change the half-open behavior of an existing `Duplex` stream instance, but must be changed before the `'end'` event is emitted. **Since** v0.9.4 | `Duplex.allowHalfOpen` | node_modules/@types/node/stream.d.ts:1076 | +| `captureRejections` | `static` | `boolean` | Value: [boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) Change the default `captureRejections` option on all new `EventEmitter` objects. **Since** v13.4.0, v12.16.0 | `Duplex.captureRejections` | node_modules/@types/node/events.d.ts:459 | +| `captureRejectionSymbol` | `readonly` | _typeof_ `captureRejectionSymbol` | Value: `Symbol.for('nodejs.rejection')` See how to write a custom `rejection handler`. **Since** v13.4.0, v12.16.0 | `Duplex.captureRejectionSymbol` | node_modules/@types/node/events.d.ts:452 | +| `closed` | `readonly` | `boolean` | Is `true` after `'close'` has been emitted. **Since** v18.0.0 | `Duplex.closed` | node_modules/@types/node/stream.d.ts:1065 | +| `defaultMaxListeners` | `static` | `number` | By default, a maximum of `10` listeners can be registered for any single event. This limit can be changed for individual `EventEmitter` instances using the `emitter.setMaxListeners(n)` method. To change the default for _all_`EventEmitter` instances, the `events.defaultMaxListeners` property can be used. If this value is not a positive number, a `RangeError` is thrown. Take caution when setting the `events.defaultMaxListeners` because the change affects _all_ `EventEmitter` instances, including those created before the change is made. However, calling `emitter.setMaxListeners(n)` still has precedence over `events.defaultMaxListeners`. This is not a hard limit. The `EventEmitter` instance will allow more listeners to be added but will output a trace warning to stderr indicating that a "possible EventEmitter memory leak" has been detected. For any single `EventEmitter`, the `emitter.getMaxListeners()` and `emitter.setMaxListeners()` methods can be used to temporarily avoid this warning: `import { EventEmitter } from 'node:events'; const emitter = new EventEmitter(); emitter.setMaxListeners(emitter.getMaxListeners() + 1); emitter.once('event', () => { // do stuff emitter.setMaxListeners(Math.max(emitter.getMaxListeners() - 1, 0)); });` The `--trace-warnings` command-line flag can be used to display the stack trace for such warnings. The emitted warning can be inspected with `process.on('warning')` and will have the additional `emitter`, `type`, and `count` properties, referring to the event emitter instance, the event's name and the number of attached listeners, respectively. Its `name` property is set to `'MaxListenersExceededWarning'`. **Since** v0.11.2 | `Duplex.defaultMaxListeners` | node_modules/@types/node/events.d.ts:498 | +| `destroyed` | `public` | `boolean` | Is `true` after `readable.destroy()` has been called. **Since** v8.0.0 | `Duplex.destroyed` | node_modules/@types/node/stream.d.ts:121 | +| `errored` | `readonly` | `null` \| `Error` | Returns error if the stream has been destroyed with an error. **Since** v18.0.0 | `Duplex.errored` | node_modules/@types/node/stream.d.ts:1066 | +| `errorMonitor` | `readonly` | _typeof_ `errorMonitor` | This symbol shall be used to install a listener for only monitoring `'error'` events. Listeners installed using this symbol are called before the regular `'error'` listeners are called. Installing a listener using this symbol does not change the behavior once an `'error'` event is emitted. Therefore, the process will still crash if no regular `'error'` listener is installed. **Since** v13.6.0, v12.17.0 | `Duplex.errorMonitor` | node_modules/@types/node/events.d.ts:445 | +| `isPiped` | `public` | `boolean` | - | - | [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) | +| `name?` | `public` | `string` | - | - | [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) | +| `readable` | `public` | `boolean` | Is `true` if it is safe to call [read](#read), which means the stream has not been destroyed or emitted `'error'` or `'end'`. **Since** v11.4.0 | `Duplex.readable` | node_modules/@types/node/stream.d.ts:77 | +| `readableAborted` | `readonly` | `boolean` | **Experimental** Returns whether the stream was destroyed or errored before emitting `'end'`. **Since** v16.8.0 | `Duplex.readableAborted` | node_modules/@types/node/stream.d.ts:71 | +| `readableDidRead` | `readonly` | `boolean` | **Experimental** Returns whether `'data'` has been emitted. **Since** v16.7.0, v14.18.0 | `Duplex.readableDidRead` | node_modules/@types/node/stream.d.ts:83 | +| `readableEncoding` | `readonly` | `null` \| `BufferEncoding` | Getter for the property `encoding` of a given `Readable` stream. The `encoding` property can be set using the [setEncoding](#setencoding) method. **Since** v12.7.0 | `Duplex.readableEncoding` | node_modules/@types/node/stream.d.ts:88 | +| `readableEnded` | `readonly` | `boolean` | Becomes `true` when [`'end'`](https://nodejs.org/docs/latest-v20.x/api/stream.html#event-end) event is emitted. **Since** v12.9.0 | `Duplex.readableEnded` | node_modules/@types/node/stream.d.ts:93 | +| `readableFlowing` | `readonly` | `null` \| `boolean` | This property reflects the current state of a `Readable` stream as described in the [Three states](https://nodejs.org/docs/latest-v20.x/api/stream.html#three-states) section. **Since** v9.4.0 | `Duplex.readableFlowing` | node_modules/@types/node/stream.d.ts:99 | +| `readableHighWaterMark` | `readonly` | `number` | Returns the value of `highWaterMark` passed when creating this `Readable`. **Since** v9.3.0 | `Duplex.readableHighWaterMark` | node_modules/@types/node/stream.d.ts:104 | +| `readableLength` | `readonly` | `number` | This property contains the number of bytes (or objects) in the queue ready to be read. The value provides introspection data regarding the status of the `highWaterMark`. **Since** v9.4.0 | `Duplex.readableLength` | node_modules/@types/node/stream.d.ts:111 | +| `readableObjectMode` | `readonly` | `boolean` | Getter for the property `objectMode` of a given `Readable` stream. **Since** v12.3.0 | `Duplex.readableObjectMode` | node_modules/@types/node/stream.d.ts:116 | +| `writable` | `readonly` | `boolean` | Is `true` if it is safe to call `writable.write()`, which means the stream has not been destroyed, errored, or ended. **Since** v11.4.0 | `Duplex.writable` | node_modules/@types/node/stream.d.ts:1057 | +| `writableCorked` | `readonly` | `number` | Number of times `writable.uncork()` needs to be called in order to fully uncork the stream. **Since** v13.2.0, v12.16.0 | `Duplex.writableCorked` | node_modules/@types/node/stream.d.ts:1063 | +| `writableEnded` | `readonly` | `boolean` | Is `true` after `writable.end()` has been called. This property does not indicate whether the data has been flushed, for this use `writable.writableFinished` instead. **Since** v12.9.0 | `Duplex.writableEnded` | node_modules/@types/node/stream.d.ts:1058 | +| `writableFinished` | `readonly` | `boolean` | Is set to `true` immediately before the `'finish'` event is emitted. **Since** v12.6.0 | `Duplex.writableFinished` | node_modules/@types/node/stream.d.ts:1059 | +| `writableHighWaterMark` | `readonly` | `number` | Return the value of `highWaterMark` passed when creating this `Writable`. **Since** v9.3.0 | `Duplex.writableHighWaterMark` | node_modules/@types/node/stream.d.ts:1060 | +| `writableLength` | `readonly` | `number` | This property contains the number of bytes (or objects) in the queue ready to be written. The value provides introspection data regarding the status of the `highWaterMark`. **Since** v9.4.0 | `Duplex.writableLength` | node_modules/@types/node/stream.d.ts:1061 | +| `writableNeedDrain` | `readonly` | `boolean` | Is `true` if the stream's buffer has been full and stream will emit `'drain'`. **Since** v15.2.0, v14.17.0 | `Duplex.writableNeedDrain` | node_modules/@types/node/stream.d.ts:1064 | +| `writableObjectMode` | `readonly` | `boolean` | Getter for the property `objectMode` of a given `Writable` stream. **Since** v12.3.0 | `Duplex.writableObjectMode` | node_modules/@types/node/stream.d.ts:1062 | + +#### Accessors + +##### hasError + +###### Get Signature + +```ts +get hasError(): boolean +``` + +**Returns:** `boolean` +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### hasInfo + +###### Get Signature + +```ts +get hasInfo(): boolean +``` + +**Returns:** `boolean` +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### hasLog + +###### Get Signature + +```ts +get hasLog(): boolean +``` + +**Returns:** `boolean` +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### hasWarning + +###### Get Signature + +```ts +get hasWarning(): boolean +``` + +**Returns:** `boolean` +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### verbosity + +###### Set Signature + +```ts +set verbosity(verbosity): void +``` + +**Parameters:** + +| Parameter | Type | +| ----------- | --------------------------- | +| `verbosity` | [`Verbosity`](#verbosity-2) | + +**Returns:** `void` +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +#### Methods + +##### \_construct()? + +```ts +optional _construct(callback): void +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | -------------------- | +| `callback` | (`error`?) => `void` | + +**Returns:** `void` + +###### Inherited from + +`Duplex._construct` + +**Defined in:** node_modules/@types/node/stream.d.ts:133 + +##### \_destroy() + +```ts +_destroy(error, callback): void +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | -------------------- | +| `error` | `null` \| `Error` | +| `callback` | (`error`?) => `void` | + +**Returns:** `void` + +###### Inherited from + +`Duplex._destroy` + +**Defined in:** node_modules/@types/node/stream.d.ts:1119 + +##### \_final() + +```ts +_final(callback): void +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `callback` | () => `void` | + +**Returns:** `void` + +###### Overrides + +`Duplex._final` + +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### \_read() + +```ts +_read(): void +``` + +**Returns:** `void` + +###### Overrides + +`Duplex._read` + +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### \_write() + +```ts +_write( + chunk, + encoding, + callback): void +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------------------------------- | +| `chunk` | `string` \| `Buffer`\<`ArrayBufferLike`\> | +| `encoding` | `undefined` \| `string` | +| `callback` | () => `void` | + +**Returns:** `void` + +###### Overrides + +`Duplex._write` + +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### \_writev()? + +```ts +optional _writev(chunks, callback): void +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------------------------------------------- | +| `chunks` | \{ `chunk`: `any`; `encoding`: `BufferEncoding`; \}[] | +| `callback` | (`error`?) => `void` | + +**Returns:** `void` + +###### Inherited from + +`Duplex._writev` + +**Defined in:** node_modules/@types/node/stream.d.ts:1112 + +##### \[asyncDispose\]() + +```ts +asyncDispose: Promise; +``` + +Calls `readable.destroy()` with an `AbortError` and returns a promise that fulfills when the stream is finished. + +**Returns:** `Promise`\<`void`\> + +###### Since + +v20.4.0 + +###### Inherited from + +`Duplex.[asyncDispose]` + +**Defined in:** node_modules/@types/node/stream.d.ts:659 + +##### \[asyncIterator\]() + +```ts +asyncIterator: AsyncIterator; +``` + +**Returns:** `AsyncIterator`\<`any`, `any`, `any`\> + +###### Inherited from + +`Duplex.[asyncIterator]` + +**Defined in:** node_modules/@types/node/stream.d.ts:654 + +##### \[captureRejectionSymbol\]()? + +```ts +optional [captureRejectionSymbol]( + error, + event, ... + args): void +``` + +###### Type Parameters + +| Type Parameter | +| -------------- | +| `K` | + +**Parameters:** + +| Parameter | Type | +| --------- | -------------------- | +| `error` | `Error` | +| `event` | `string` \| `symbol` | +| ...`args` | `AnyRest` | + +**Returns:** `void` + +###### Inherited from + +`Duplex.[captureRejectionSymbol]` + +**Defined in:** node_modules/@types/node/events.d.ts:136 + +##### addAbortListener() + +```ts +static addAbortListener(signal, resource): Disposable +``` + +**Experimental** + +Listens once to the `abort` event on the provided `signal`. + +Listening to the `abort` event on abort signals is unsafe and may +lead to resource leaks since another third party with the signal can +call `e.stopImmediatePropagation()`. Unfortunately Node.js cannot change +this since it would violate the web standard. Additionally, the original +API makes it easy to forget to remove listeners. + +This API allows safely using `AbortSignal`s in Node.js APIs by solving these +two issues by listening to the event such that `stopImmediatePropagation` does +not prevent the listener from running. + +Returns a disposable so that it may be unsubscribed from more easily. + +```js +import { addAbortListener } from 'node:events'; + +function example(signal) { + let disposable; + try { + signal.addEventListener('abort', (e) => e.stopImmediatePropagation()); + disposable = addAbortListener(signal, (e) => { + // Do something when signal is aborted. + }); + } finally { + disposable?.[Symbol.dispose](); + } +} +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------------- | +| `signal` | `AbortSignal` | +| `resource` | (`event`) => `void` | + +**Returns:** `Disposable` + +Disposable that removes the `abort` listener. + +###### Since + +v20.5.0 + +###### Inherited from + +`Duplex.addAbortListener` + +**Defined in:** node_modules/@types/node/events.d.ts:437 + +##### addListener() + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"close"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1168 + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------------- | +| `event` | `"data"` | +| `listener` | (`chunk`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1169 + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"drain"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1170 + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"end"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1171 + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"error"` | +| `listener` | (`err`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1172 + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"finish"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1173 + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"pause"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1174 + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"pipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1175 + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"readable"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1176 + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"resume"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1177 + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"unpipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1178 + +###### Call Signature + +```ts +addListener(event, listener): this +``` + +Event emitter +The defined events on documents including: + +1. close +2. data +3. drain +4. end +5. error +6. finish +7. pause +8. pipe +9. readable +10. resume +11. unpipe + +**Parameters:** + +| Parameter | Type | +| ---------- | --------------------- | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.addListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1179 + +##### asIndexedPairs() + +```ts +asIndexedPairs(options?): Readable +``` + +This method returns a new stream with chunks of the underlying stream paired with a counter +in the form `[index, chunk]`. The first index value is `0` and it increases by 1 for each chunk produced. + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------------------------------ | +| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | + +**Returns:** `Readable` + +a stream of indexed pairs. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.asIndexedPairs` + +**Defined in:** node_modules/@types/node/stream.d.ts:549 + +##### compose() + +```ts +compose(stream, options?): T +``` + +###### Type Parameters + +| Type Parameter | +| ------------------------------ | +| `T` _extends_ `ReadableStream` | + +**Parameters:** + +| Parameter | Type | +| ----------------- | -------------------------------------------------------------------------------------------------- | +| `stream` | `T` \| `ComposeFnParam` \| `Iterable`\<`T`, `any`, `any`\> \| `AsyncIterable`\<`T`, `any`, `any`\> | +| `options`? | \{ `signal`: `AbortSignal`; \} | +| `options.signal`? | `AbortSignal` | + +**Returns:** `T` + +###### Inherited from + +`Duplex.compose` + +**Defined in:** node_modules/@types/node/stream.d.ts:36 + +##### cork() + +```ts +cork(): void +``` + +The `writable.cork()` method forces all written data to be buffered in memory. +The buffered data will be flushed when either the [uncork](#uncork) or [end](#end) methods are called. + +The primary intent of `writable.cork()` is to accommodate a situation in which +several small chunks are written to the stream in rapid succession. Instead of +immediately forwarding them to the underlying destination, `writable.cork()` buffers all the chunks until `writable.uncork()` is called, which will pass them +all to `writable._writev()`, if present. This prevents a head-of-line blocking +situation where data is being buffered while waiting for the first small chunk +to be processed. However, use of `writable.cork()` without implementing `writable._writev()` may have an adverse effect on throughput. + +See also: `writable.uncork()`, `writable._writev()`. + +**Returns:** `void` + +###### Since + +v0.11.2 + +###### Inherited from + +`Duplex.cork` + +**Defined in:** node_modules/@types/node/stream.d.ts:1127 + +##### debug() + +```ts +debug(contents): void +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | --------- | +| `contents` | `unknown` | + +**Returns:** `void` +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### destroy() + +```ts +destroy(error?): this +``` + +Destroy the stream. Optionally emit an `'error'` event, and emit a `'close'` event (unless `emitClose` is set to `false`). After this call, the readable +stream will release any internal resources and subsequent calls to `push()` will be ignored. + +Once `destroy()` has been called any further calls will be a no-op and no +further errors except from `_destroy()` may be emitted as `'error'`. + +Implementors should not override this method, but instead implement `readable._destroy()`. + +**Parameters:** + +| Parameter | Type | Description | +| --------- | ------- | -------------------------------------------------------- | +| `error`? | `Error` | Error which will be passed as payload in `'error'` event | + +**Returns:** `this` + +###### Since + +v8.0.0 + +###### Inherited from + +`Duplex.destroy` + +**Defined in:** node_modules/@types/node/stream.d.ts:586 + +##### drop() + +```ts +drop(limit, options?): Readable +``` + +This method returns a new stream with the first _limit_ chunks dropped from the start. + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ------------------------------------ | ----------------------------------------------- | +| `limit` | `number` | the number of chunks to drop from the readable. | +| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | - | + +**Returns:** `Readable` + +a stream with _limit_ chunks dropped from the start. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.drop` + +**Defined in:** node_modules/@types/node/stream.d.ts:535 + +##### emit() + +###### Call Signature + +```ts +emit(event): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | --------- | +| `event` | `"close"` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1180 + +###### Call Signature + +```ts +emit(event, chunk): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | -------- | +| `event` | `"data"` | +| `chunk` | `any` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1181 + +###### Call Signature + +```ts +emit(event): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | --------- | +| `event` | `"drain"` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1182 + +###### Call Signature + +```ts +emit(event): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | ------- | +| `event` | `"end"` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1183 + +###### Call Signature + +```ts +emit(event, err): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | --------- | +| `event` | `"error"` | +| `err` | `Error` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1184 + +###### Call Signature + +```ts +emit(event): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | ---------- | +| `event` | `"finish"` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1185 + +###### Call Signature + +```ts +emit(event): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | --------- | +| `event` | `"pause"` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1186 + +###### Call Signature + +```ts +emit(event, src): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | ---------- | +| `event` | `"pipe"` | +| `src` | `Readable` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1187 + +###### Call Signature + +```ts +emit(event): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | ------------ | +| `event` | `"readable"` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1188 + +###### Call Signature + +```ts +emit(event): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | ---------- | +| `event` | `"resume"` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1189 + +###### Call Signature + +```ts +emit(event, src): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | ---------- | +| `event` | `"unpipe"` | +| `src` | `Readable` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1190 + +###### Call Signature + +```ts +emit(event, ...args): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | -------------------- | +| `event` | `string` \| `symbol` | +| ...`args` | `any`[] | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.emit` + +**Defined in:** node_modules/@types/node/stream.d.ts:1191 + +##### end() + +```ts +end(): this +``` + +**Returns:** `this` + +###### Overrides + +`Duplex.end` + +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### error() + +```ts +error(contents): void +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | --------- | +| `contents` | `unknown` | + +**Returns:** `void` +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### eventNames() + +```ts +eventNames(): (string | symbol)[] +``` + +Returns an array listing the events for which the emitter has registered +listeners. The values in the array are strings or `Symbol`s. + +```js +import { EventEmitter } from 'node:events'; + +const myEE = new EventEmitter(); +myEE.on('foo', () => {}); +myEE.on('bar', () => {}); + +const sym = Symbol('symbol'); +myEE.on(sym, () => {}); + +console.log(myEE.eventNames()); +// Prints: [ 'foo', 'bar', Symbol(symbol) ] +``` + +**Returns:** (`string` \| `symbol`)[] + +###### Since + +v6.0.0 + +###### Inherited from + +`Duplex.eventNames` + +**Defined in:** node_modules/@types/node/events.d.ts:922 + +##### every() + +```ts +every(fn, options?): Promise +``` + +This method is similar to `Array.prototype.every` and calls _fn_ on each chunk in the stream +to check if all awaited return values are truthy value for _fn_. Once an _fn_ call on a chunk +`await`ed return value is falsy, the stream is destroyed and the promise is fulfilled with `false`. +If all of the _fn_ calls on the chunks return a truthy value, the promise is fulfilled with `true`. + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ----------------------------------------------------------- | ------------------------------------------------------------- | +| `fn` | (`data`, `options`?) => `boolean` \| `Promise`\<`boolean`\> | a function to call on each chunk of the stream. Async or not. | +| `options`? | `ArrayOptions` | - | + +**Returns:** `Promise`\<`boolean`\> + +a promise evaluating to `true` if _fn_ returned a truthy value for every one of the chunks. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.every` + +**Defined in:** node_modules/@types/node/stream.d.ts:514 + +##### filter() + +```ts +filter(fn, options?): Readable +``` + +This method allows filtering the stream. For each chunk in the stream the _fn_ function will be called +and if it returns a truthy value, the chunk will be passed to the result stream. +If the _fn_ function returns a promise - that promise will be `await`ed. + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ----------------------------------------------------------- | ---------------------------------------------------------- | +| `fn` | (`data`, `options`?) => `boolean` \| `Promise`\<`boolean`\> | a function to filter chunks from the stream. Async or not. | +| `options`? | `ArrayOptions` | - | + +**Returns:** `Readable` + +a stream filtered with the predicate _fn_. + +###### Since + +v17.4.0, v16.14.0 + +###### Inherited from + +`Duplex.filter` + +**Defined in:** node_modules/@types/node/stream.d.ts:442 + +##### find() + +###### Call Signature + +```ts +find(fn, options?): Promise +``` + +This method is similar to `Array.prototype.find` and calls _fn_ on each chunk in the stream +to find a chunk with a truthy value for _fn_. Once an _fn_ call's awaited return value is truthy, +the stream is destroyed and the promise is fulfilled with value for which _fn_ returned a truthy value. +If all of the _fn_ calls on the chunks return a falsy value, the promise is fulfilled with `undefined`. + +###### Type Parameters + +| Type Parameter | +| -------------- | +| `T` | + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ----------------------------------- | ------------------------------------------------------------- | +| `fn` | (`data`, `options`?) => `data is T` | a function to call on each chunk of the stream. Async or not. | +| `options`? | `ArrayOptions` | - | + +**Returns:** `Promise`\<`undefined` \| `T`\> + +a promise evaluating to the first chunk for which _fn_ evaluated with a truthy value, +or `undefined` if no element was found. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.find` + +**Defined in:** node_modules/@types/node/stream.d.ts:497 + +###### Call Signature + +```ts +find(fn, options?): Promise +``` + +This method is similar to `Array.prototype.find` and calls _fn_ on each chunk in the stream +to find a chunk with a truthy value for _fn_. Once an _fn_ call's awaited return value is truthy, +the stream is destroyed and the promise is fulfilled with value for which _fn_ returned a truthy value. +If all of the _fn_ calls on the chunks return a falsy value, the promise is fulfilled with `undefined`. + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ----------------------------------------------------------- | ------------------------------------------------------------- | +| `fn` | (`data`, `options`?) => `boolean` \| `Promise`\<`boolean`\> | a function to call on each chunk of the stream. Async or not. | +| `options`? | `ArrayOptions` | - | + +**Returns:** `Promise`\<`any`\> + +a promise evaluating to the first chunk for which _fn_ evaluated with a truthy value, +or `undefined` if no element was found. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.find` + +**Defined in:** node_modules/@types/node/stream.d.ts:501 + +##### flatMap() + +```ts +flatMap(fn, options?): Readable +``` + +This method returns a new stream by applying the given callback to each chunk of the stream +and then flattening the result. + +It is possible to return a stream or another iterable or async iterable from _fn_ and the result streams +will be merged (flattened) into the returned stream. + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ----------------------------- | --------------------------------------------------------------------------------------------- | +| `fn` | (`data`, `options`?) => `any` | a function to map over every chunk in the stream. May be async. May be a stream or generator. | +| `options`? | `ArrayOptions` | - | + +**Returns:** `Readable` + +a stream flat-mapped with the function _fn_. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.flatMap` + +**Defined in:** node_modules/@types/node/stream.d.ts:528 + +##### forEach() + +```ts +forEach(fn, options?): Promise +``` + +This method allows iterating a stream. For each chunk in the stream the _fn_ function will be called. +If the _fn_ function returns a promise - that promise will be `await`ed. + +This method is different from `for await...of` loops in that it can optionally process chunks concurrently. +In addition, a `forEach` iteration can only be stopped by having passed a `signal` option +and aborting the related AbortController while `for await...of` can be stopped with `break` or `return`. +In either case the stream will be destroyed. + +This method is different from listening to the `'data'` event in that it uses the `readable` event +in the underlying machinary and can limit the number of concurrent _fn_ calls. + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ----------------------------------------------------- | ------------------------------------------------------------- | +| `fn` | (`data`, `options`?) => `void` \| `Promise`\<`void`\> | a function to call on each chunk of the stream. Async or not. | +| `options`? | `ArrayOptions` | - | + +**Returns:** `Promise`\<`void`\> + +a promise for when the stream has finished. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.forEach` + +**Defined in:** node_modules/@types/node/stream.d.ts:461 + +##### from() + +```ts +static from(src): Duplex +``` + +A utility method for creating duplex streams. + +- `Stream` converts writable stream into writable `Duplex` and readable stream + to `Duplex`. +- `Blob` converts into readable `Duplex`. +- `string` converts into readable `Duplex`. +- `ArrayBuffer` converts into readable `Duplex`. +- `AsyncIterable` converts into a readable `Duplex`. Cannot yield `null`. +- `AsyncGeneratorFunction` converts into a readable/writable transform + `Duplex`. Must take a source `AsyncIterable` as first parameter. Cannot yield + `null`. +- `AsyncFunction` converts into a writable `Duplex`. Must return + either `null` or `undefined` +- `Object ({ writable, readable })` converts `readable` and + `writable` into `Stream` and then combines them into `Duplex` where the + `Duplex` will write to the `writable` and read from the `readable`. +- `Promise` converts into readable `Duplex`. Value `null` is ignored. + +**Parameters:** + +| Parameter | Type | +| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `src` | \| `string` \| `Object` \| `Promise`\<`any`\> \| `ArrayBuffer` \| `Stream` \| `Blob` \| `Iterable`\<`any`, `any`, `any`\> \| `AsyncIterable`\<`any`, `any`, `any`\> \| `AsyncGeneratorFunction` | + +**Returns:** `Duplex` + +###### Since + +v16.8.0 + +###### Inherited from + +`Duplex.from` + +**Defined in:** node_modules/@types/node/stream.d.ts:1099 + +##### fromWeb() + +```ts +static fromWeb(duplexStream, options?): Duplex +``` + +**Experimental** + +A utility method for creating a `Duplex` from a web `ReadableStream` and `WritableStream`. + +**Parameters:** + +| Parameter | Type | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `duplexStream` | \{ `readable`: `ReadableStream`\<`any`\>; `writable`: `WritableStream`\<`any`\>; \} | +| `duplexStream.readable` | `ReadableStream`\<`any`\> | +| `duplexStream.writable`? | `WritableStream`\<`any`\> | +| `options`? | `Pick`\<`DuplexOptions`, \| `"signal"` \| `"allowHalfOpen"` \| `"decodeStrings"` \| `"encoding"` \| `"highWaterMark"` \| `"objectMode"`\> | + +**Returns:** `Duplex` + +###### Since + +v17.0.0 + +###### Inherited from + +`Duplex.fromWeb` + +**Defined in:** node_modules/@types/node/stream.d.ts:1143 + +##### getEventListeners() + +```ts +static getEventListeners(emitter, name): Function[] +``` + +Returns a copy of the array of listeners for the event named `eventName`. + +For `EventEmitter`s this behaves exactly the same as calling `.listeners` on +the emitter. + +For `EventTarget`s this is the only way to get the event listeners for the +event target. This is useful for debugging and diagnostic purposes. + +```js +import { getEventListeners, EventEmitter } from 'node:events'; + +{ + const ee = new EventEmitter(); + const listener = () => console.log('Events are fun'); + ee.on('foo', listener); + console.log(getEventListeners(ee, 'foo')); // [ [Function: listener] ] +} +{ + const et = new EventTarget(); + const listener = () => console.log('Events are fun'); + et.addEventListener('foo', listener); + console.log(getEventListeners(et, 'foo')); // [ [Function: listener] ] +} +``` + +**Parameters:** + +| Parameter | Type | +| --------- | ---------------------------------------------------- | +| `emitter` | `EventEmitter`\<`DefaultEventMap`\> \| `EventTarget` | +| `name` | `string` \| `symbol` | + +**Returns:** `Function`[] + +###### Since + +v15.2.0, v14.17.0 + +###### Inherited from + +`Duplex.getEventListeners` + +**Defined in:** node_modules/@types/node/events.d.ts:358 + +##### getMaxListeners() + +```ts +static getMaxListeners(emitter): number +``` + +Returns the currently set max amount of listeners. + +For `EventEmitter`s this behaves exactly the same as calling `.getMaxListeners` on +the emitter. + +For `EventTarget`s this is the only way to get the max event listeners for the +event target. If the number of event handlers on a single EventTarget exceeds +the max set, the EventTarget will print a warning. + +```js +import { getMaxListeners, setMaxListeners, EventEmitter } from 'node:events'; + +{ + const ee = new EventEmitter(); + console.log(getMaxListeners(ee)); // 10 + setMaxListeners(11, ee); + console.log(getMaxListeners(ee)); // 11 +} +{ + const et = new EventTarget(); + console.log(getMaxListeners(et)); // 10 + setMaxListeners(11, et); + console.log(getMaxListeners(et)); // 11 +} +``` + +**Parameters:** + +| Parameter | Type | +| --------- | ---------------------------------------------------- | +| `emitter` | `EventEmitter`\<`DefaultEventMap`\> \| `EventTarget` | + +**Returns:** `number` + +###### Since + +v19.9.0 + +###### Inherited from + +`Duplex.getMaxListeners` + +**Defined in:** node_modules/@types/node/events.d.ts:387 + +##### getMaxListeners() + +```ts +getMaxListeners(): number +``` + +Returns the current max listener value for the `EventEmitter` which is either +set by `emitter.setMaxListeners(n)` or defaults to [defaultMaxListeners](#logstep). + +**Returns:** `number` + +###### Since + +v1.0.0 + +###### Inherited from + +`Duplex.getMaxListeners` + +**Defined in:** node_modules/@types/node/events.d.ts:774 + +##### info() + +```ts +info(contents): void +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | --------- | +| `contents` | `unknown` | + +**Returns:** `void` +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### isDisturbed() + +```ts +static isDisturbed(stream): boolean +``` + +Returns whether the stream has been read from or cancelled. + +**Parameters:** + +| Parameter | Type | +| --------- | ------------------------------ | +| `stream` | `ReadableStream` \| `Readable` | + +**Returns:** `boolean` + +###### Since + +v16.8.0 + +###### Inherited from + +`Duplex.isDisturbed` + +**Defined in:** node_modules/@types/node/stream.d.ts:65 + +##### isPaused() + +```ts +isPaused(): boolean +``` + +The `readable.isPaused()` method returns the current operating state of the `Readable`. +This is used primarily by the mechanism that underlies the `readable.pipe()` method. +In most typical cases, there will be no reason to use this method directly. + +```js +const readable = new stream.Readable(); + +readable.isPaused(); // === false +readable.pause(); +readable.isPaused(); // === true +readable.resume(); +readable.isPaused(); // === false +``` + +**Returns:** `boolean` + +###### Since + +v0.11.14 + +###### Inherited from + +`Duplex.isPaused` + +**Defined in:** node_modules/@types/node/stream.d.ts:295 + +##### iterator() + +```ts +iterator(options?): AsyncIterator +``` + +The iterator created by this method gives users the option to cancel the destruction +of the stream if the `for await...of` loop is exited by `return`, `break`, or `throw`, +or if the iterator should destroy the stream if the stream emitted an error during iteration. + +**Parameters:** + +| Parameter | Type | Description | +| -------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `options`? | \{ `destroyOnReturn`: `boolean`; \} | - | +| `options.destroyOnReturn`? | `boolean` | When set to `false`, calling `return` on the async iterator, or exiting a `for await...of` iteration using a `break`, `return`, or `throw` will not destroy the stream. **Default: `true`**. | + +**Returns:** `AsyncIterator`\<`any`, `any`, `any`\> + +###### Since + +v16.3.0 + +###### Inherited from + +`Duplex.iterator` + +**Defined in:** node_modules/@types/node/stream.d.ts:425 + +##### ~~listenerCount()~~ + +```ts +static listenerCount(emitter, eventName): number +``` + +A class method that returns the number of listeners for the given `eventName` registered on the given `emitter`. + +```js +import { EventEmitter, listenerCount } from 'node:events'; + +const myEmitter = new EventEmitter(); +myEmitter.on('event', () => {}); +myEmitter.on('event', () => {}); +console.log(listenerCount(myEmitter, 'event')); +// Prints: 2 +``` + +**Parameters:** + +| Parameter | Type | Description | +| ----------- | ----------------------------------- | -------------------- | +| `emitter` | `EventEmitter`\<`DefaultEventMap`\> | The emitter to query | +| `eventName` | `string` \| `symbol` | The event name | + +**Returns:** `number` + +###### Since + +v0.9.12 + +###### Deprecated + +Since v3.2.0 - Use `listenerCount` instead. + +###### Inherited from + +`Duplex.listenerCount` + +**Defined in:** node_modules/@types/node/events.d.ts:330 + +##### listenerCount() + +```ts +listenerCount(eventName, listener?): number +``` + +Returns the number of listeners listening for the event named `eventName`. +If `listener` is provided, it will return how many times the listener is found +in the list of the listeners of the event. + +###### Type Parameters + +| Type Parameter | +| -------------- | +| `K` | + +**Parameters:** + +| Parameter | Type | Description | +| ----------- | -------------------- | ---------------------------------------- | +| `eventName` | `string` \| `symbol` | The name of the event being listened for | +| `listener`? | `Function` | The event handler function | + +**Returns:** `number` + +###### Since + +v3.2.0 + +###### Inherited from + +`Duplex.listenerCount` + +**Defined in:** node_modules/@types/node/events.d.ts:868 + +##### listeners() + +```ts +listeners(eventName): Function[] +``` + +Returns a copy of the array of listeners for the event named `eventName`. + +```js +server.on('connection', (stream) => { + console.log('someone connected!'); +}); +console.log(util.inspect(server.listeners('connection'))); +// Prints: [ [Function] ] +``` + +###### Type Parameters + +| Type Parameter | +| -------------- | +| `K` | + +**Parameters:** + +| Parameter | Type | +| ----------- | -------------------- | +| `eventName` | `string` \| `symbol` | + +**Returns:** `Function`[] + +###### Since + +v0.1.26 + +###### Inherited from + +`Duplex.listeners` + +**Defined in:** node_modules/@types/node/events.d.ts:787 + +##### log() + +```ts +log(contents): void +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | --------- | +| `contents` | `unknown` | + +**Returns:** `void` +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### map() + +```ts +map(fn, options?): Readable +``` + +This method allows mapping over the stream. The _fn_ function will be called for every chunk in the stream. +If the _fn_ function returns a promise - that promise will be `await`ed before being passed to the result stream. + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ----------------------------- | --------------------------------------------------------------- | +| `fn` | (`data`, `options`?) => `any` | a function to map over every chunk in the stream. Async or not. | +| `options`? | `ArrayOptions` | - | + +**Returns:** `Readable` + +a stream mapped with the function _fn_. + +###### Since + +v17.4.0, v16.14.0 + +###### Inherited from + +`Duplex.map` + +**Defined in:** node_modules/@types/node/stream.d.ts:433 + +##### off() + +```ts +off(eventName, listener): this +``` + +Alias for `emitter.removeListener()`. + +###### Type Parameters + +| Type Parameter | +| -------------- | +| `K` | + +**Parameters:** + +| Parameter | Type | +| ----------- | --------------------- | +| `eventName` | `string` \| `symbol` | +| `listener` | (...`args`) => `void` | + +**Returns:** `this` + +###### Since + +v10.0.0 + +###### Inherited from + +`Duplex.off` + +**Defined in:** node_modules/@types/node/events.d.ts:747 + +##### on() + +###### Call Signature + +```ts +static on( + emitter, + eventName, +options?): AsyncIterator +``` + +```js +import { on, EventEmitter } from 'node:events'; +import process from 'node:process'; + +const ee = new EventEmitter(); + +// Emit later on +process.nextTick(() => { + ee.emit('foo', 'bar'); + ee.emit('foo', 42); +}); + +for await (const event of on(ee, 'foo')) { + // The execution of this inner block is synchronous and it + // processes one event at a time (even with await). Do not use + // if concurrent execution is required. + console.log(event); // prints ['bar'] [42] +} +// Unreachable here +``` + +Returns an `AsyncIterator` that iterates `eventName` events. It will throw +if the `EventEmitter` emits `'error'`. It removes all listeners when +exiting the loop. The `value` returned by each iteration is an array +composed of the emitted event arguments. + +An `AbortSignal` can be used to cancel waiting on events: + +```js +import { on, EventEmitter } from 'node:events'; +import process from 'node:process'; + +const ac = new AbortController(); + +(async () => { + const ee = new EventEmitter(); + + // Emit later on + process.nextTick(() => { + ee.emit('foo', 'bar'); + ee.emit('foo', 42); + }); + + for await (const event of on(ee, 'foo', { signal: ac.signal })) { + // The execution of this inner block is synchronous and it + // processes one event at a time (even with await). Do not use + // if concurrent execution is required. + console.log(event); // prints ['bar'] [42] + } + // Unreachable here +})(); + +process.nextTick(() => ac.abort()); +``` + +Use the `close` option to specify an array of event names that will end the iteration: + +```js +import { on, EventEmitter } from 'node:events'; +import process from 'node:process'; + +const ee = new EventEmitter(); + +// Emit later on +process.nextTick(() => { + ee.emit('foo', 'bar'); + ee.emit('foo', 42); + ee.emit('close'); +}); + +for await (const event of on(ee, 'foo', { close: ['close'] })) { + console.log(event); // prints ['bar'] [42] +} +// the loop will exit after 'close' is emitted +console.log('done'); // prints 'done' +``` + +**Parameters:** + +| Parameter | Type | +| ----------- | ----------------------------------- | +| `emitter` | `EventEmitter`\<`DefaultEventMap`\> | +| `eventName` | `string` \| `symbol` | +| `options`? | `StaticEventEmitterIteratorOptions` | + +**Returns:** `AsyncIterator`\<`any`[], `any`, `any`\> + +An `AsyncIterator` that iterates `eventName` events emitted by the `emitter` + +###### Since + +v13.6.0, v12.16.0 + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/events.d.ts:303 + +###### Call Signature + +```ts +static on( + emitter, + eventName, +options?): AsyncIterator +``` + +```js +import { on, EventEmitter } from 'node:events'; +import process from 'node:process'; + +const ee = new EventEmitter(); + +// Emit later on +process.nextTick(() => { + ee.emit('foo', 'bar'); + ee.emit('foo', 42); +}); + +for await (const event of on(ee, 'foo')) { + // The execution of this inner block is synchronous and it + // processes one event at a time (even with await). Do not use + // if concurrent execution is required. + console.log(event); // prints ['bar'] [42] +} +// Unreachable here +``` + +Returns an `AsyncIterator` that iterates `eventName` events. It will throw +if the `EventEmitter` emits `'error'`. It removes all listeners when +exiting the loop. The `value` returned by each iteration is an array +composed of the emitted event arguments. + +An `AbortSignal` can be used to cancel waiting on events: + +```js +import { on, EventEmitter } from 'node:events'; +import process from 'node:process'; + +const ac = new AbortController(); + +(async () => { + const ee = new EventEmitter(); + + // Emit later on + process.nextTick(() => { + ee.emit('foo', 'bar'); + ee.emit('foo', 42); + }); + + for await (const event of on(ee, 'foo', { signal: ac.signal })) { + // The execution of this inner block is synchronous and it + // processes one event at a time (even with await). Do not use + // if concurrent execution is required. + console.log(event); // prints ['bar'] [42] + } + // Unreachable here +})(); + +process.nextTick(() => ac.abort()); +``` + +Use the `close` option to specify an array of event names that will end the iteration: + +```js +import { on, EventEmitter } from 'node:events'; +import process from 'node:process'; + +const ee = new EventEmitter(); + +// Emit later on +process.nextTick(() => { + ee.emit('foo', 'bar'); + ee.emit('foo', 42); + ee.emit('close'); +}); + +for await (const event of on(ee, 'foo', { close: ['close'] })) { + console.log(event); // prints ['bar'] [42] +} +// the loop will exit after 'close' is emitted +console.log('done'); // prints 'done' +``` + +**Parameters:** + +| Parameter | Type | +| ----------- | ----------------------------------- | +| `emitter` | `EventTarget` | +| `eventName` | `string` | +| `options`? | `StaticEventEmitterIteratorOptions` | + +**Returns:** `AsyncIterator`\<`any`[], `any`, `any`\> + +An `AsyncIterator` that iterates `eventName` events emitted by the `emitter` + +###### Since + +v13.6.0, v12.16.0 + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/events.d.ts:308 + +##### on() + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"close"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1192 + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------------- | +| `event` | `"data"` | +| `listener` | (`chunk`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1193 + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"drain"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1194 + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"end"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1195 + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"error"` | +| `listener` | (`err`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1196 + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"finish"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1197 + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"pause"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1198 + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"pipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1199 + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"readable"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1200 + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"resume"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1201 + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"unpipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1202 + +###### Call Signature + +```ts +on(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | --------------------- | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.on` + +**Defined in:** node_modules/@types/node/stream.d.ts:1203 + +##### once() + +###### Call Signature + +```ts +static once( + emitter, + eventName, +options?): Promise +``` + +Creates a `Promise` that is fulfilled when the `EventEmitter` emits the given +event or that is rejected if the `EventEmitter` emits `'error'` while waiting. +The `Promise` will resolve with an array of all the arguments emitted to the +given event. + +This method is intentionally generic and works with the web platform [EventTarget](https://dom.spec.whatwg.org/#interface-eventtarget) interface, which has no special`'error'` event +semantics and does not listen to the `'error'` event. + +```js +import { once, EventEmitter } from 'node:events'; +import process from 'node:process'; + +const ee = new EventEmitter(); + +process.nextTick(() => { + ee.emit('myevent', 42); +}); + +const [value] = await once(ee, 'myevent'); +console.log(value); + +const err = new Error('kaboom'); +process.nextTick(() => { + ee.emit('error', err); +}); + +try { + await once(ee, 'myevent'); +} catch (err) { + console.error('error happened', err); +} +``` + +The special handling of the `'error'` event is only used when `events.once()` is used to wait for another event. If `events.once()` is used to wait for the +'`error'` event itself, then it is treated as any other kind of event without +special handling: + +```js +import { EventEmitter, once } from 'node:events'; + +const ee = new EventEmitter(); + +once(ee, 'error') + .then(([err]) => console.log('ok', err.message)) + .catch((err) => console.error('error', err.message)); + +ee.emit('error', new Error('boom')); + +// Prints: ok boom +``` + +An `AbortSignal` can be used to cancel waiting for the event: + +```js +import { EventEmitter, once } from 'node:events'; + +const ee = new EventEmitter(); +const ac = new AbortController(); + +async function foo(emitter, event, signal) { + try { + await once(emitter, event, { signal }); + console.log('event emitted!'); + } catch (error) { + if (error.name === 'AbortError') { + console.error('Waiting for the event was canceled!'); + } else { + console.error('There was an error', error.message); + } + } +} + +foo(ee, 'foo', ac.signal); +ac.abort(); // Abort waiting for the event +ee.emit('foo'); // Prints: Waiting for the event was canceled! +``` + +**Parameters:** + +| Parameter | Type | +| ----------- | ----------------------------------- | +| `emitter` | `EventEmitter`\<`DefaultEventMap`\> | +| `eventName` | `string` \| `symbol` | +| `options`? | `StaticEventEmitterOptions` | + +**Returns:** `Promise`\<`any`[]\> + +###### Since + +v11.13.0, v10.16.0 + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/events.d.ts:217 + +###### Call Signature + +```ts +static once( + emitter, + eventName, +options?): Promise +``` + +Creates a `Promise` that is fulfilled when the `EventEmitter` emits the given +event or that is rejected if the `EventEmitter` emits `'error'` while waiting. +The `Promise` will resolve with an array of all the arguments emitted to the +given event. + +This method is intentionally generic and works with the web platform [EventTarget](https://dom.spec.whatwg.org/#interface-eventtarget) interface, which has no special`'error'` event +semantics and does not listen to the `'error'` event. + +```js +import { once, EventEmitter } from 'node:events'; +import process from 'node:process'; + +const ee = new EventEmitter(); + +process.nextTick(() => { + ee.emit('myevent', 42); +}); + +const [value] = await once(ee, 'myevent'); +console.log(value); + +const err = new Error('kaboom'); +process.nextTick(() => { + ee.emit('error', err); +}); + +try { + await once(ee, 'myevent'); +} catch (err) { + console.error('error happened', err); +} +``` + +The special handling of the `'error'` event is only used when `events.once()` is used to wait for another event. If `events.once()` is used to wait for the +'`error'` event itself, then it is treated as any other kind of event without +special handling: + +```js +import { EventEmitter, once } from 'node:events'; + +const ee = new EventEmitter(); + +once(ee, 'error') + .then(([err]) => console.log('ok', err.message)) + .catch((err) => console.error('error', err.message)); + +ee.emit('error', new Error('boom')); + +// Prints: ok boom +``` + +An `AbortSignal` can be used to cancel waiting for the event: + +```js +import { EventEmitter, once } from 'node:events'; + +const ee = new EventEmitter(); +const ac = new AbortController(); + +async function foo(emitter, event, signal) { + try { + await once(emitter, event, { signal }); + console.log('event emitted!'); + } catch (error) { + if (error.name === 'AbortError') { + console.error('Waiting for the event was canceled!'); + } else { + console.error('There was an error', error.message); + } + } +} + +foo(ee, 'foo', ac.signal); +ac.abort(); // Abort waiting for the event +ee.emit('foo'); // Prints: Waiting for the event was canceled! +``` + +**Parameters:** + +| Parameter | Type | +| ----------- | --------------------------- | +| `emitter` | `EventTarget` | +| `eventName` | `string` | +| `options`? | `StaticEventEmitterOptions` | + +**Returns:** `Promise`\<`any`[]\> + +###### Since + +v11.13.0, v10.16.0 + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/events.d.ts:222 + +##### once() + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"close"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1204 + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------------- | +| `event` | `"data"` | +| `listener` | (`chunk`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1205 + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"drain"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1206 + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"end"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1207 + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"error"` | +| `listener` | (`err`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1208 + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"finish"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1209 + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"pause"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1210 + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"pipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1211 + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"readable"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1212 + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"resume"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1213 + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"unpipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1214 + +###### Call Signature + +```ts +once(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | --------------------- | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.once` + +**Defined in:** node_modules/@types/node/stream.d.ts:1215 + +##### pause() + +```ts +pause(): this +``` + +The `readable.pause()` method will cause a stream in flowing mode to stop +emitting `'data'` events, switching out of flowing mode. Any data that +becomes available will remain in the internal buffer. + +```js +const readable = getReadableStreamSomehow(); +readable.on('data', (chunk) => { + console.log(`Received ${chunk.length} bytes of data.`); + readable.pause(); + console.log('There will be no additional data for 1 second.'); + setTimeout(() => { + console.log('Now data will start flowing again.'); + readable.resume(); + }, 1000); +}); +``` + +The `readable.pause()` method has no effect if there is a `'readable'` event listener. + +**Returns:** `this` + +###### Since + +v0.9.4 + +###### Inherited from + +`Duplex.pause` + +**Defined in:** node_modules/@types/node/stream.d.ts:259 + +##### pipe() + +```ts +pipe(destination, options?): T +``` + +###### Type Parameters + +| Type Parameter | +| ------------------------------ | +| `T` _extends_ `WritableStream` | + +**Parameters:** + +| Parameter | Type | +| -------------- | ----------------------- | +| `destination` | `T` | +| `options`? | \{ `end`: `boolean`; \} | +| `options.end`? | `boolean` | + +**Returns:** `T` + +###### Inherited from + +`Duplex.pipe` + +**Defined in:** node_modules/@types/node/stream.d.ts:30 + +##### prependListener() + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"close"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1216 + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------------- | +| `event` | `"data"` | +| `listener` | (`chunk`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1217 + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"drain"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1218 + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"end"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1219 + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"error"` | +| `listener` | (`err`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1220 + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"finish"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1221 + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"pause"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1222 + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"pipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1223 + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"readable"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1224 + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"resume"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1225 + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"unpipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1226 + +###### Call Signature + +```ts +prependListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | --------------------- | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1227 + +##### prependOnceListener() + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"close"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1228 + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------------- | +| `event` | `"data"` | +| `listener` | (`chunk`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1229 + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"drain"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1230 + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"end"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1231 + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"error"` | +| `listener` | (`err`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1232 + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"finish"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1233 + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"pause"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1234 + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"pipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1235 + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"readable"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1236 + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"resume"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1237 + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"unpipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1238 + +###### Call Signature + +```ts +prependOnceListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | --------------------- | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.prependOnceListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1239 + +##### push() + +```ts +push(chunk, encoding?): boolean +``` + +**Parameters:** + +| Parameter | Type | +| ----------- | ---------------- | +| `chunk` | `any` | +| `encoding`? | `BufferEncoding` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.push` + +**Defined in:** node_modules/@types/node/stream.d.ts:415 + +##### rawListeners() + +```ts +rawListeners(eventName): Function[] +``` + +Returns a copy of the array of listeners for the event named `eventName`, +including any wrappers (such as those created by `.once()`). + +```js +import { EventEmitter } from 'node:events'; +const emitter = new EventEmitter(); +emitter.once('log', () => console.log('log once')); + +// Returns a new Array with a function `onceWrapper` which has a property +// `listener` which contains the original listener bound above +const listeners = emitter.rawListeners('log'); +const logFnWrapper = listeners[0]; + +// Logs "log once" to the console and does not unbind the `once` event +logFnWrapper.listener(); + +// Logs "log once" to the console and removes the listener +logFnWrapper(); + +emitter.on('log', () => console.log('log persistently')); +// Will return a new Array with a single function bound by `.on()` above +const newListeners = emitter.rawListeners('log'); + +// Logs "log persistently" twice +newListeners[0](); +emitter.emit('log'); +``` + +###### Type Parameters + +| Type Parameter | +| -------------- | +| `K` | + +**Parameters:** + +| Parameter | Type | +| ----------- | -------------------- | +| `eventName` | `string` \| `symbol` | + +**Returns:** `Function`[] + +###### Since + +v9.4.0 + +###### Inherited from + +`Duplex.rawListeners` + +**Defined in:** node_modules/@types/node/events.d.ts:818 + +##### read() + +```ts +read(size?): any +``` + +The `readable.read()` method reads data out of the internal buffer and +returns it. If no data is available to be read, `null` is returned. By default, +the data is returned as a `Buffer` object unless an encoding has been +specified using the `readable.setEncoding()` method or the stream is operating +in object mode. + +The optional `size` argument specifies a specific number of bytes to read. If +`size` bytes are not available to be read, `null` will be returned _unless_ the +stream has ended, in which case all of the data remaining in the internal buffer +will be returned. + +If the `size` argument is not specified, all of the data contained in the +internal buffer will be returned. + +The `size` argument must be less than or equal to 1 GiB. + +The `readable.read()` method should only be called on `Readable` streams +operating in paused mode. In flowing mode, `readable.read()` is called +automatically until the internal buffer is fully drained. + +```js +const readable = getReadableStreamSomehow(); + +// 'readable' may be triggered multiple times as data is buffered in +readable.on('readable', () => { + let chunk; + console.log('Stream is readable (new data received in buffer)'); + // Use a loop to make sure we read all currently available data + while (null !== (chunk = readable.read())) { + console.log(`Read ${chunk.length} bytes of data...`); + } +}); + +// 'end' will be triggered once when there is no more data available +readable.on('end', () => { + console.log('Reached end of stream.'); +}); +``` + +Each call to `readable.read()` returns a chunk of data, or `null`. The chunks +are not concatenated. A `while` loop is necessary to consume all data +currently in the buffer. When reading a large file `.read()` may return `null`, +having consumed all buffered content so far, but there is still more data to +come not yet buffered. In this case a new `'readable'` event will be emitted +when there is more data in the buffer. Finally the `'end'` event will be +emitted when there is no more data to come. + +Therefore to read a file's whole contents from a `readable`, it is necessary +to collect chunks across multiple `'readable'` events: + +```js +const chunks = []; + +readable.on('readable', () => { + let chunk; + while (null !== (chunk = readable.read())) { + chunks.push(chunk); + } +}); + +readable.on('end', () => { + const content = chunks.join(''); +}); +``` + +A `Readable` stream in object mode will always return a single item from +a call to `readable.read(size)`, regardless of the value of the `size` argument. + +If the `readable.read()` method returns a chunk of data, a `'data'` event will +also be emitted. + +Calling [read](#read) after the `'end'` event has +been emitted will return `null`. No runtime error will be raised. + +**Parameters:** + +| Parameter | Type | Description | +| --------- | -------- | --------------------------------------------------- | +| `size`? | `number` | Optional argument to specify how much data to read. | + +**Returns:** `any` + +###### Since + +v0.9.4 + +###### Inherited from + +`Duplex.read` + +**Defined in:** node_modules/@types/node/stream.d.ts:212 + +##### reduce() + +###### Call Signature + +```ts +reduce( + fn, + initial?, +options?): Promise +``` + +This method calls _fn_ on each chunk of the stream in order, passing it the result from the calculation +on the previous element. It returns a promise for the final value of the reduction. + +If no _initial_ value is supplied the first chunk of the stream is used as the initial value. +If the stream is empty, the promise is rejected with a `TypeError` with the `ERR_INVALID_ARGS` code property. + +The reducer function iterates the stream element-by-element which means that there is no _concurrency_ parameter +or parallelism. To perform a reduce concurrently, you can extract the async function to `readable.map` method. + +###### Type Parameters + +| Type Parameter | Default type | +| -------------- | ------------ | +| `T` | `any` | + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | --------------------------------------- | ------------------------------------------------------------------------ | +| `fn` | (`previous`, `data`, `options`?) => `T` | a reducer function to call over every chunk in the stream. Async or not. | +| `initial`? | `undefined` | the initial value to use in the reduction. | +| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | - | + +**Returns:** `Promise`\<`T`\> + +a promise for the final value of the reduction. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.reduce` + +**Defined in:** node_modules/@types/node/stream.d.ts:564 + +###### Call Signature + +```ts +reduce( + fn, + initial, +options?): Promise +``` + +This method calls _fn_ on each chunk of the stream in order, passing it the result from the calculation +on the previous element. It returns a promise for the final value of the reduction. + +If no _initial_ value is supplied the first chunk of the stream is used as the initial value. +If the stream is empty, the promise is rejected with a `TypeError` with the `ERR_INVALID_ARGS` code property. + +The reducer function iterates the stream element-by-element which means that there is no _concurrency_ parameter +or parallelism. To perform a reduce concurrently, you can extract the async function to `readable.map` method. + +###### Type Parameters + +| Type Parameter | Default type | +| -------------- | ------------ | +| `T` | `any` | + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | --------------------------------------- | ------------------------------------------------------------------------ | +| `fn` | (`previous`, `data`, `options`?) => `T` | a reducer function to call over every chunk in the stream. Async or not. | +| `initial` | `T` | the initial value to use in the reduction. | +| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | - | + +**Returns:** `Promise`\<`T`\> + +a promise for the final value of the reduction. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.reduce` + +**Defined in:** node_modules/@types/node/stream.d.ts:569 + +##### removeAllListeners() + +```ts +removeAllListeners(eventName?): this +``` + +Removes all listeners, or those of the specified `eventName`. + +It is bad practice to remove listeners added elsewhere in the code, +particularly when the `EventEmitter` instance was created by some other +component or module (e.g. sockets or file streams). + +Returns a reference to the `EventEmitter`, so that calls can be chained. + +**Parameters:** + +| Parameter | Type | +| ------------ | -------------------- | +| `eventName`? | `string` \| `symbol` | + +**Returns:** `this` + +###### Since + +v0.1.26 + +###### Inherited from + +`Duplex.removeAllListeners` + +**Defined in:** node_modules/@types/node/events.d.ts:758 + +##### removeListener() + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"close"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1240 + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------------- | +| `event` | `"data"` | +| `listener` | (`chunk`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1241 + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"drain"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1242 + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"end"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1243 + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"error"` | +| `listener` | (`err`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1244 + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"finish"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1245 + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"pause"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1246 + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"pipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1247 + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"readable"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1248 + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------ | +| `event` | `"resume"` | +| `listener` | () => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1249 + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | ----------------- | +| `event` | `"unpipe"` | +| `listener` | (`src`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1250 + +###### Call Signature + +```ts +removeListener(event, listener): this +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | --------------------- | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`) => `void` | + +**Returns:** `this` + +###### Inherited from + +`Duplex.removeListener` + +**Defined in:** node_modules/@types/node/stream.d.ts:1251 + +##### resume() + +```ts +resume(): this +``` + +The `readable.resume()` method causes an explicitly paused `Readable` stream to +resume emitting `'data'` events, switching the stream into flowing mode. + +The `readable.resume()` method can be used to fully consume the data from a +stream without actually processing any of that data: + +```js +getReadableStreamSomehow() + .resume() + .on('end', () => { + console.log('Reached the end, but did not read anything.'); + }); +``` + +The `readable.resume()` method has no effect if there is a `'readable'` event listener. + +**Returns:** `this` + +###### Since + +v0.9.4 + +###### Inherited from + +`Duplex.resume` + +**Defined in:** node_modules/@types/node/stream.d.ts:278 + +##### setDefaultEncoding() + +```ts +setDefaultEncoding(encoding): this +``` + +The `writable.setDefaultEncoding()` method sets the default `encoding` for a `Writable` stream. + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ---------------- | ------------------------ | +| `encoding` | `BufferEncoding` | The new default encoding | + +**Returns:** `this` + +###### Since + +v0.11.15 + +###### Inherited from + +`Duplex.setDefaultEncoding` + +**Defined in:** node_modules/@types/node/stream.d.ts:1123 + +##### setEncoding() + +```ts +setEncoding(encoding): this +``` + +The `readable.setEncoding()` method sets the character encoding for +data read from the `Readable` stream. + +By default, no encoding is assigned and stream data will be returned as `Buffer` objects. Setting an encoding causes the stream data +to be returned as strings of the specified encoding rather than as `Buffer` objects. For instance, calling `readable.setEncoding('utf8')` will cause the +output data to be interpreted as UTF-8 data, and passed as strings. Calling `readable.setEncoding('hex')` will cause the data to be encoded in hexadecimal +string format. + +The `Readable` stream will properly handle multi-byte characters delivered +through the stream that would otherwise become improperly decoded if simply +pulled from the stream as `Buffer` objects. + +```js +const readable = getReadableStreamSomehow(); +readable.setEncoding('utf8'); +readable.on('data', (chunk) => { + assert.equal(typeof chunk, 'string'); + console.log('Got %d characters of string data:', chunk.length); +}); +``` + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ---------------- | -------------------- | +| `encoding` | `BufferEncoding` | The encoding to use. | + +**Returns:** `this` + +###### Since + +v0.9.4 + +###### Inherited from + +`Duplex.setEncoding` + +**Defined in:** node_modules/@types/node/stream.d.ts:237 + +##### setMaxListeners() + +```ts +static setMaxListeners(n?, ...eventTargets?): void +``` + +```js +import { setMaxListeners, EventEmitter } from 'node:events'; + +const target = new EventTarget(); +const emitter = new EventEmitter(); + +setMaxListeners(5, target, emitter); +``` + +**Parameters:** + +| Parameter | Type | Description | +| ------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `n`? | `number` | A non-negative number. The maximum number of listeners per `EventTarget` event. | +| ...`eventTargets`? | (`EventEmitter`\<`DefaultEventMap`\> \| `EventTarget`)[] | Zero or more {EventTarget} or {EventEmitter} instances. If none are specified, `n` is set as the default max for all newly created {EventTarget} and {EventEmitter} objects. | + +**Returns:** `void` + +###### Since + +v15.4.0 + +###### Inherited from + +`Duplex.setMaxListeners` + +**Defined in:** node_modules/@types/node/events.d.ts:402 + +##### setMaxListeners() + +```ts +setMaxListeners(n): this +``` + +By default `EventEmitter`s will print a warning if more than `10` listeners are +added for a particular event. This is a useful default that helps finding +memory leaks. The `emitter.setMaxListeners()` method allows the limit to be +modified for this specific `EventEmitter` instance. The value can be set to `Infinity` (or `0`) to indicate an unlimited number of listeners. + +Returns a reference to the `EventEmitter`, so that calls can be chained. + +**Parameters:** + +| Parameter | Type | +| --------- | -------- | +| `n` | `number` | + +**Returns:** `this` + +###### Since + +v0.3.5 + +###### Inherited from + +`Duplex.setMaxListeners` + +**Defined in:** node_modules/@types/node/events.d.ts:768 + +##### some() + +```ts +some(fn, options?): Promise +``` + +This method is similar to `Array.prototype.some` and calls _fn_ on each chunk in the stream +until the awaited return value is `true` (or any truthy value). Once an _fn_ call on a chunk +`await`ed return value is truthy, the stream is destroyed and the promise is fulfilled with `true`. +If none of the _fn_ calls on the chunks return a truthy value, the promise is fulfilled with `false`. + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ----------------------------------------------------------- | ------------------------------------------------------------- | +| `fn` | (`data`, `options`?) => `boolean` \| `Promise`\<`boolean`\> | a function to call on each chunk of the stream. Async or not. | +| `options`? | `ArrayOptions` | - | + +**Returns:** `Promise`\<`boolean`\> + +a promise evaluating to `true` if _fn_ returned a truthy value for at least one of the chunks. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.some` + +**Defined in:** node_modules/@types/node/stream.d.ts:483 + +##### take() + +```ts +take(limit, options?): Readable +``` + +This method returns a new stream with the first _limit_ chunks. + +**Parameters:** + +| Parameter | Type | Description | +| ---------- | ------------------------------------ | ----------------------------------------------- | +| `limit` | `number` | the number of chunks to take from the readable. | +| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | - | + +**Returns:** `Readable` + +a stream with _limit_ chunks taken. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.take` + +**Defined in:** node_modules/@types/node/stream.d.ts:542 + +##### timing() + +```ts +timing(start, end): void +``` + +**Parameters:** + +| Parameter | Type | +| --------- | -------- | +| `start` | `string` | +| `end` | `string` | + +**Returns:** `void` +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### toArray() + +```ts +toArray(options?): Promise +``` + +This method allows easily obtaining the contents of a stream. + +As this method reads the entire stream into memory, it negates the benefits of streams. It's intended +for interoperability and convenience, not as the primary way to consume streams. + +**Parameters:** + +| Parameter | Type | +| ---------- | ------------------------------------ | +| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | + +**Returns:** `Promise`\<`any`[]\> + +a promise containing an array with the contents of the stream. + +###### Since + +v17.5.0 + +###### Inherited from + +`Duplex.toArray` + +**Defined in:** node_modules/@types/node/stream.d.ts:473 + +##### toWeb() + +```ts +static toWeb(streamDuplex): { + readable: ReadableStream; + writable: WritableStream; +} +``` + +**Experimental** + +A utility method for creating a web `ReadableStream` and `WritableStream` from a `Duplex`. + +**Parameters:** + +| Parameter | Type | +| -------------- | -------- | +| `streamDuplex` | `Duplex` | + +**Returns:** ```ts +{ +readable: ReadableStream; +writable: WritableStream; +} + +```` + +###### readable + +```ts +readable: ReadableStream; +```` + +###### writable + +```ts +writable: WritableStream; +``` + +###### Since + +v17.0.0 + +###### Inherited from + +`Duplex.toWeb` + +**Defined in:** node_modules/@types/node/stream.d.ts:1134 + +##### uncork() + +```ts +uncork(): void +``` + +The `writable.uncork()` method flushes all data buffered since [cork](#cork) was called. + +When using `writable.cork()` and `writable.uncork()` to manage the buffering +of writes to a stream, defer calls to `writable.uncork()` using `process.nextTick()`. Doing so allows batching of all `writable.write()` calls that occur within a given Node.js event +loop phase. + +```js +stream.cork(); +stream.write('some '); +stream.write('data '); +process.nextTick(() => stream.uncork()); +``` + +If the `writable.cork()` method is called multiple times on a stream, the +same number of calls to `writable.uncork()` must be called to flush the buffered +data. + +```js +stream.cork(); +stream.write('some '); +stream.cork(); +stream.write('data '); +process.nextTick(() => { + stream.uncork(); + // The data will not be flushed until uncork() is called a second time. + stream.uncork(); +}); +``` + +See also: `writable.cork()`. + +**Returns:** `void` + +###### Since + +v0.11.2 + +###### Inherited from + +`Duplex.uncork` + +**Defined in:** node_modules/@types/node/stream.d.ts:1128 + +##### unpipe() + +```ts +unpipe(destination?): this +``` + +The `readable.unpipe()` method detaches a `Writable` stream previously attached +using the [pipe](#pipe) method. + +If the `destination` is not specified, then _all_ pipes are detached. + +If the `destination` is specified, but no pipe is set up for it, then +the method does nothing. + +```js +import fs from 'node:fs'; +const readable = getReadableStreamSomehow(); +const writable = fs.createWriteStream('file.txt'); +// All the data from readable goes into 'file.txt', +// but only for the first second. +readable.pipe(writable); +setTimeout(() => { + console.log('Stop writing to file.txt.'); + readable.unpipe(writable); + console.log('Manually close the file stream.'); + writable.end(); +}, 1000); +``` + +**Parameters:** + +| Parameter | Type | Description | +| -------------- | ---------------- | ---------------------------------- | +| `destination`? | `WritableStream` | Optional specific stream to unpipe | + +**Returns:** `this` + +###### Since + +v0.9.4 + +###### Inherited from + +`Duplex.unpipe` + +**Defined in:** node_modules/@types/node/stream.d.ts:322 + +##### unshift() + +```ts +unshift(chunk, encoding?): void +``` + +Passing `chunk` as `null` signals the end of the stream (EOF) and behaves the +same as `readable.push(null)`, after which no more data can be written. The EOF +signal is put at the end of the buffer and any buffered data will still be +flushed. + +The `readable.unshift()` method pushes a chunk of data back into the internal +buffer. This is useful in certain situations where a stream is being consumed by +code that needs to "un-consume" some amount of data that it has optimistically +pulled out of the source, so that the data can be passed on to some other party. + +The `stream.unshift(chunk)` method cannot be called after the `'end'` event +has been emitted or a runtime error will be thrown. + +Developers using `stream.unshift()` often should consider switching to +use of a `Transform` stream instead. See the `API for stream implementers` section for more information. + +```js +// Pull off a header delimited by \n\n. +// Use unshift() if we get too much. +// Call the callback with (error, header, stream). +import { StringDecoder } from 'node:string_decoder'; +function parseHeader(stream, callback) { + stream.on('error', callback); + stream.on('readable', onReadable); + const decoder = new StringDecoder('utf8'); + let header = ''; + function onReadable() { + let chunk; + while (null !== (chunk = stream.read())) { + const str = decoder.write(chunk); + if (str.includes('\n\n')) { + // Found the header boundary. + const split = str.split(/\n\n/); + header += split.shift(); + const remaining = split.join('\n\n'); + const buf = Buffer.from(remaining, 'utf8'); + stream.removeListener('error', callback); + // Remove the 'readable' listener before unshifting. + stream.removeListener('readable', onReadable); + if (buf.length) stream.unshift(buf); + // Now the body of the message can be read from the stream. + callback(null, header, stream); + return; + } + // Still reading the header. + header += str; + } + } +} +``` + +Unlike [push](#push), `stream.unshift(chunk)` will not +end the reading process by resetting the internal reading state of the stream. +This can cause unexpected results if `readable.unshift()` is called during a +read (i.e. from within a [\_read](#_read) implementation on a +custom stream). Following the call to `readable.unshift()` with an immediate [push](#push) will reset the reading state appropriately, +however it is best to simply avoid calling `readable.unshift()` while in the +process of performing a read. + +**Parameters:** + +| Parameter | Type | Description | +| ----------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `chunk` | `any` | Chunk of data to unshift onto the read queue. For streams not operating in object mode, `chunk` must be a {string}, {Buffer}, {TypedArray}, {DataView} or `null`. For object mode streams, `chunk` may be any JavaScript value. | +| `encoding`? | `BufferEncoding` | Encoding of string chunks. Must be a valid `Buffer` encoding, such as `'utf8'` or `'ascii'`. | + +**Returns:** `void` + +###### Since + +v0.9.11 + +###### Inherited from + +`Duplex.unshift` + +**Defined in:** node_modules/@types/node/stream.d.ts:388 + +##### warn() + +```ts +warn(contents): void +``` + +**Parameters:** + +| Parameter | Type | +| ---------- | --------- | +| `contents` | `unknown` | + +**Returns:** `void` +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +##### wrap() + +```ts +wrap(stream): this +``` + +Prior to Node.js 0.10, streams did not implement the entire `node:stream` module API as it is currently defined. (See `Compatibility` for more +information.) + +When using an older Node.js library that emits `'data'` events and has a [pause](#pause) method that is advisory only, the `readable.wrap()` method can be used to create a `Readable` +stream that uses +the old stream as its data source. + +It will rarely be necessary to use `readable.wrap()` but the method has been +provided as a convenience for interacting with older Node.js applications and +libraries. + +```js +import { OldReader } from './old-api-module.js'; +import { Readable } from 'node:stream'; +const oreader = new OldReader(); +const myReader = new Readable().wrap(oreader); + +myReader.on('readable', () => { + myReader.read(); // etc. +}); +``` + +**Parameters:** + +| Parameter | Type | Description | +| --------- | ---------------- | ------------------------------ | +| `stream` | `ReadableStream` | An "old style" readable stream | + +**Returns:** `this` + +###### Since + +v0.9.4 + +###### Inherited from + +`Duplex.wrap` + +**Defined in:** node_modules/@types/node/stream.d.ts:414 + +##### write() + +###### Call Signature + +```ts +write( + chunk, + encoding?, + cb?): boolean +``` + +The `writable.write()` method writes some data to the stream, and calls the +supplied `callback` once the data has been fully handled. If an error +occurs, the `callback` will be called with the error as its +first argument. The `callback` is called asynchronously and before `'error'` is +emitted. + +The return value is `true` if the internal buffer is less than the `highWaterMark` configured when the stream was created after admitting `chunk`. +If `false` is returned, further attempts to write data to the stream should +stop until the `'drain'` event is emitted. + +While a stream is not draining, calls to `write()` will buffer `chunk`, and +return false. Once all currently buffered chunks are drained (accepted for +delivery by the operating system), the `'drain'` event will be emitted. +Once `write()` returns false, do not write more chunks +until the `'drain'` event is emitted. While calling `write()` on a stream that +is not draining is allowed, Node.js will buffer all written chunks until +maximum memory usage occurs, at which point it will abort unconditionally. +Even before it aborts, high memory usage will cause poor garbage collector +performance and high RSS (which is not typically released back to the system, +even after the memory is no longer required). Since TCP sockets may never +drain if the remote peer does not read the data, writing a socket that is +not draining may lead to a remotely exploitable vulnerability. + +Writing data while the stream is not draining is particularly +problematic for a `Transform`, because the `Transform` streams are paused +by default until they are piped or a `'data'` or `'readable'` event handler +is added. + +If the data to be written can be generated or fetched on demand, it is +recommended to encapsulate the logic into a `Readable` and use [pipe](#pipe). However, if calling `write()` is preferred, it is +possible to respect backpressure and avoid memory issues using the `'drain'` event: + +```js +function write(data, cb) { + if (!stream.write(data)) { + stream.once('drain', cb); + } else { + process.nextTick(cb); + } +} + +// Wait for cb to be called before doing any other write. +write('hello', () => { + console.log('Write completed, do more writes now.'); +}); +``` + +A `Writable` stream in object mode will always ignore the `encoding` argument. + +**Parameters:** + +| Parameter | Type | Description | +| ----------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `chunk` | `any` | Optional data to write. For streams not operating in object mode, `chunk` must be a {string}, {Buffer}, {TypedArray} or {DataView}. For object mode streams, `chunk` may be any JavaScript value other than `null`. | +| `encoding`? | `BufferEncoding` | The encoding, if `chunk` is a string. | +| `cb`? | (`error`) => `void` | - | + +**Returns:** `boolean` + +`false` if the stream wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`. + +###### Since + +v0.9.4 + +###### Inherited from + +`Duplex.write` + +**Defined in:** node_modules/@types/node/stream.d.ts:1121 + +###### Call Signature + +```ts +write(chunk, cb?): boolean +``` + +**Parameters:** + +| Parameter | Type | +| --------- | ------------------- | +| `chunk` | `any` | +| `cb`? | (`error`) => `void` | + +**Returns:** `boolean` + +###### Inherited from + +`Duplex.write` + +**Defined in:** node_modules/@types/node/stream.d.ts:1122 + +## Type Aliases + +### LineType + +```ts +type LineType: + | "start" + | "end" + | "error" + | "warn" + | "info" + | "log" + | "debug" + | "timing"; +``` + +**Defined in:** [modules/logger/src/types.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/types.ts) + +--- + +### LoggedBuffer + +```ts +type LoggedBuffer: { + contents: string; + group: string; + hasError: boolean; + type: LineType; + verbosity: Verbosity; +}; +``` + +#### Type declaration + +##### contents + +```ts +contents: string; +``` + +##### group? + +```ts +optional group: string; +``` + +##### hasError? + +```ts +optional hasError: boolean; +``` + +##### type + +```ts +type: LineType; +``` + +##### verbosity + +```ts +verbosity: Verbosity; +``` + +**Defined in:** [modules/logger/src/types.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/types.ts) + +--- + +### LogStepOptions + +```ts +type LogStepOptions: { + description: string; + name: string; + verbosity: Verbosity; +}; +``` + +#### Type declaration + +##### description? + +```ts +optional description: string; +``` + +##### name + +```ts +name: string; +``` + +##### verbosity + +```ts +verbosity: Verbosity; +``` + +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) ## Variables @@ -16,6 +4835,32 @@ const defaultConfig: Required; **Defined in:** [modules/onerepo/src/setup/setup.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/setup/setup.ts) +## Functions + +### restoreCursor() + +```ts +function restoreCursor(): void; +``` + +Gracefully restore the CLI cursor on exit. + +Prevent the cursor you have hidden interactively from remaining hidden if the process crashes. + +It does nothing if run in a non-TTY context. + +**Returns:** `void` + +#### Example + +``` +import restoreCursor from 'restore-cursor'; + +restoreCursor(); +``` + +**Defined in:** [modules/logger/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/index.ts) + ## Commands ### Argv\ @@ -2032,6 +6877,19 @@ See [\`HandlerExtra\`](#handlerextra) for access the the global Logger instance. #### Accessors +##### captureAll + +###### Get Signature + +```ts +get captureAll(): boolean +``` + +**Experimental** +**Returns:** `boolean` + +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) + ##### hasError ###### Get Signature @@ -2094,7 +6952,7 @@ get verbosity(): Verbosity Get the logger's verbosity level -**Returns:** [`Verbosity`](#verbosity-1) +**Returns:** [`Verbosity`](#verbosity-2) ###### Set Signature @@ -2108,7 +6966,7 @@ Recursively applies the new verbosity to the logger and all of its active steps. | Parameter | Type | | --------- | --------------------------- | -| `value` | [`Verbosity`](#verbosity-1) | +| `value` | [`Verbosity`](#verbosity-2) | **Returns:** `void` **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) @@ -2129,7 +6987,7 @@ get writable(): boolean ##### createStep() ```ts -createStep(name, __namedParameters?): LogStep +createStep(name, opts?): LogStep ``` Create a sub-step, [\`LogStep\`](#logstep), for the logger. This and any other step will be tracked and required to finish before exit. @@ -2142,11 +7000,11 @@ await step.end(); **Parameters:** -| Parameter | Type | Description | -| ---------------------------------- | --------------------------------- | ----------------------------------------------------------------------------- | -| `name` | `string` | The name to be written and wrapped around any output logged to this new step. | -| `__namedParameters`? | \{ `writePrefixes`: `boolean`; \} | - | -| `__namedParameters.writePrefixes`? | `boolean` | - | +| Parameter | Type | Description | +| --------------------- | --------------------------------- | ----------------------------------------------------------------------------- | +| `name` | `string` | The name to be written and wrapped around any output logged to this new step. | +| `opts`? | \{ `writePrefixes`: `boolean`; \} | - | +| `opts.writePrefixes`? | `boolean` | - | **Returns:** [`LogStep`](#logstep) **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) @@ -2154,7 +7012,7 @@ await step.end(); ##### pause() ```ts -pause(write?): void +pause(): void ``` When the terminal is a TTY, steps are automatically animated with a progress indicator. There are times when it's necessary to stop this animation, like when needing to capture user input from `stdin`. Call the `pause()` method before requesting input and [\`logger.unpause()\`](#unpause) when complete. @@ -2167,12 +7025,6 @@ logger.pause(); logger.unpause(); ``` -**Parameters:** - -| Parameter | Type | -| --------- | --------- | -| `write`? | `boolean` | - **Returns:** `void` **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) @@ -2182,7 +7034,7 @@ logger.unpause(); unpause(): void ``` -Unpause the logger and resume writing buffered logs to `stderr`. See [\`logger.pause()\`](#pause) for more information. +Unpause the logger and resume writing buffered logs to the output stream. See [\`logger.pause()\`](#pause-1) for more information. **Returns:** `void` **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) @@ -2215,7 +7067,7 @@ logger.debug(() => bigArray.map((item) => item.name)); **Returns:** `void` **See also:** -[\`debug()\`](#debug-1) This is a pass-through for the main step’s [\`debug()\`](#debug-1) method. +[\`debug()\`](#debug) This is a pass-through for the main step’s [\`debug()\`](#debug) method. **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) @@ -2245,7 +7097,7 @@ logger.error(() => bigArray.map((item) => item.name)); **Returns:** `void` **See also:** -[\`error()\`](#error-1) This is a pass-through for the main step’s [\`error()\`](#error-1) method. +[\`error()\`](#error) This is a pass-through for the main step’s [\`error()\`](#error) method. **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) @@ -2275,7 +7127,7 @@ logger.info(() => bigArray.map((item) => item.name)); **Returns:** `void` **See also:** -[\`info()\`](#info-1) This is a pass-through for the main step’s [\`info()\`](#info-1) method. +[\`info()\`](#info) This is a pass-through for the main step’s [\`info()\`](#info) method. **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) @@ -2305,7 +7157,7 @@ logger.log(() => bigArray.map((item) => item.name)); **Returns:** `void` **See also:** -[\`log()\`](#log-1) This is a pass-through for the main step’s [\`log()\`](#log-1) method. +[\`log()\`](#log) This is a pass-through for the main step’s [\`log()\`](#log) method. **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) @@ -2326,7 +7178,7 @@ Log timing information between two [Node.js performance mark names](https://node **Returns:** `void` **See also:** -[\`timing()\`](#timing-1) This is a pass-through for the main step’s [\`timing()\`](#timing-1) method. +[\`timing()\`](#timing) This is a pass-through for the main step’s [\`timing()\`](#timing) method. **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) @@ -2356,220 +7208,36 @@ logger.warn(() => bigArray.map((item) => item.name)); **Returns:** `void` **See also:** -[\`warn()\`](#warn-1) This is a pass-through for the main step’s [\`warn()\`](#warn-1) method. +[\`warn()\`](#warn) This is a pass-through for the main step’s [\`warn()\`](#warn) method. **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) --- -### LogStep - -Log steps should only be created via the [\`logger.createStep()\`](#createstep) method. - -```ts -const step = logger.createStep('Do some work'); -// ... long task with a bunch of potential output -await step.end(); -``` - -#### Properties - -| Property | Type | Description | Defined in | -| ------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -| `hasError` | `boolean` | Whether or not an error has been sent to the step. This is not necessarily indicative of uncaught thrown errors, but solely on whether `.error()` has been called in this step. | [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) | -| `hasInfo` | `boolean` | Whether or not an info message has been sent to this step. | [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) | -| `hasLog` | `boolean` | Whether or not a log message has been sent to this step. | [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) | -| `hasWarning` | `boolean` | Whether or not a warning has been sent to this step. | [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) | - -#### Methods - -##### end() - -```ts -end(): Promise -``` - -Finish this step and flush all buffered logs. Once a step is ended, it will no longer accept any logging output and will be effectively removed from the base logger. Consider this method similar to a destructor or teardown. - -```ts -await step.end(); -``` - -**Returns:** `Promise`\<`void`\> -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -#### Logging - -##### debug() - -```ts -debug(contents): void -``` - -Extra debug logging when verbosity greater than or equal to 4. - -```ts -step.debug('Log this content when verbosity is >= 4'); -``` - -If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged debug information: - -```ts -step.debug(() => bigArray.map((item) => item.name)); -``` - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | --------- | -------------------------------------------------------------------- | -| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. | - -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -##### error() - -```ts -error(contents): void -``` - -Log an error. This will cause the root logger to include an error and fail a command. - -```ts -step.error('Log this content when verbosity is >= 1'); -``` - -If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged error: - -```ts -step.error(() => bigArray.map((item) => item.name)); -``` - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | --------- | -------------------------------------------------------------------- | -| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. | - -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -##### info() - -```ts -info(contents): void -``` - -Log an informative message. Should be used when trying to convey information with a user that is important enough to always be returned. - -```ts -step.info('Log this content when verbosity is >= 1'); -``` - -If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: - -```ts -step.info(() => bigArray.map((item) => item.name)); -``` - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | --------- | -------------------------------------------------------------------- | -| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. | - -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -##### log() - -```ts -log(contents): void -``` - -General logging information. Useful for light informative debugging. Recommended to use sparingly. - -```ts -step.log('Log this content when verbosity is >= 3'); -``` - -If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: - -```ts -step.log(() => bigArray.map((item) => item.name)); -``` - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | --------- | -------------------------------------------------------------------- | -| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. | - -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -##### timing() - -```ts -timing(start, end): void -``` - -Log timing information between two [Node.js performance mark names](https://nodejs.org/dist/latest-v18.x/docs/api/perf_hooks.html#performancemarkname-options). - -**Parameters:** - -| Parameter | Type | Description | -| --------- | -------- | ------------------------------ | -| `start` | `string` | A `PerformanceMark` entry name | -| `end` | `string` | A `PerformanceMark` entry name | - -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -##### warn() - -```ts -warn(contents): void -``` - -Log a warning. Does not have any effect on the command run, but will be called out. - -```ts -step.warn('Log this content when verbosity is >= 2'); -``` - -If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged warning: - -```ts -step.warn(() => bigArray.map((item) => item.name)); -``` - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | --------- | -------------------------------------------------------------------- | -| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. | - -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - ---- - ### LoggerOptions ```ts type LoggerOptions: { - stream: Writable; + captureAll: boolean; + stream: Writable | LogStep; verbosity: Verbosity; }; ``` #### Type declaration +##### captureAll? + +```ts +optional captureAll: boolean; +``` + +**Experimental** + ##### stream? ```ts -optional stream: Writable; +optional stream: Writable | LogStep; ``` Advanced – override the writable stream in order to pipe logs elsewhere. Mostly used for dependency injection for `@onerepo/test-cli`. @@ -2609,7 +7277,7 @@ Control the verbosity of the log output | `>= 4` | Debug | `logger.debug()` will be included | | `>= 5` | Timing | Extra performance timing metrics will be written | -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/types.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/types.ts) ## Package management diff --git a/docs/src/content/docs/plugins/docgen.mdx b/docs/src/content/docs/plugins/docgen.mdx index 3bbc185f..a909396a 100644 --- a/docs/src/content/docs/plugins/docgen.mdx +++ b/docs/src/content/docs/plugins/docgen.mdx @@ -1,46 +1,54 @@ ---- +## <<<<<<< HEAD + title: '@onerepo/plugin-docgen' sidebar: - label: Documentation generation +label: Documentation generation description: Official plugin for generating documentation in the oneRepo JavaScript and TypeScript monorepo toolchain. meta: - version: 1.0.1 - stability: stable +version: 1.0.1 +stability: stable + --- -import { Tabs, TabItem } from '@astrojs/starlight/components'; +# import { Tabs, TabItem } from '@astrojs/starlight/components'; -## Installation +{/* start-auto-generated-from-cli-docgen */} +{/* @generated SignedSource<> */} - - +### `one docgen` - ```sh title="Install via npm" - npm install --save-dev @onerepo/plugin-docgen - ``` +Generate documentation for the oneRepo cli. - - +```sh +one docgen [options...] +``` - ```sh title="Install via Yarn" - yarn add --dev @onerepo/plugin-docgen - ``` +Help documentation should always be easy to find. This command will help automate the creation of docs for this command-line interface. If you are reading this somewhere that is not your terminal, there is a very good chance that this command was already run for you! - - +Add this command to your one Repo tasks on pre-commit to ensure that your documentation is always up-to-date. - ```sh title="Install via pnpm" - pnpm install --save-dev @onerepo/plugin-docgen - ``` +| Option | Type | Description | +| ----------------- | --------------------------------------------- | -------------------------------------------------------------------------- | +| `--add` | `boolean` | Add the output file to the git stage | +| `--format` | `"markdown"`, `"json"`, default: `"markdown"` | Output format for documentation | +| `--heading-level` | `number` | Heading level to start at for Markdown output | +| `--out-file` | `string` | File to write output to. If not provided, stdout will be used | +| `--out-workspace` | `string` | Workspace name to write the --out-file to | +| `--safe-write` | `boolean` | Write documentation to a portion of the file with start and end sentinels. | - - +
-:::tip[Example] -Check out our very own source level [example](example/) generated directly from the oneRepo source code using this plugin. -::: +Advanced options -## Configuration +| Option | Type | Description | +| ----------------- | --------- | ------------------------------------------------------------------------------------ | +| `--command` | `string` | Start at the given command, skip the root and any others | +| `--show-advanced` | `boolean` | Pair with `--help` to show advanced options. | +| `--use-defaults` | `boolean` | Use the oneRepo default configuration. Helpful for generating default documentation. | + +
+ +{/* end-auto-generated-from-cli-docgen */} {/* start-install-typedoc */} {/* @generated SignedSource<<9b847ec5a1fb60fd74a1bd5a66f31900>> */} @@ -142,43 +150,3 @@ optional safeWrite: boolean; Set to true to amend content to the given file using the file.writeSafe \| \`file.writeSafe\` method. {/* end-install-typedoc */} - -## Commands - -{/* start-auto-generated-from-cli-docgen */} -{/* @generated SignedSource<> */} - -### `one docgen` - -Generate documentation for the oneRepo cli. - -```sh -one docgen [options...] -``` - -Help documentation should always be easy to find. This command will help automate the creation of docs for this command-line interface. If you are reading this somewhere that is not your terminal, there is a very good chance that this command was already run for you! - -Add this command to your one Repo tasks on pre-commit to ensure that your documentation is always up-to-date. - -| Option | Type | Description | -| ----------------- | --------------------------------------------- | -------------------------------------------------------------------------- | -| `--add` | `boolean` | Add the output file to the git stage | -| `--format` | `"markdown"`, `"json"`, default: `"markdown"` | Output format for documentation | -| `--heading-level` | `number` | Heading level to start at for Markdown output | -| `--out-file` | `string` | File to write output to. If not provided, stdout will be used | -| `--out-workspace` | `string` | Workspace name to write the --out-file to | -| `--safe-write` | `boolean` | Write documentation to a portion of the file with start and end sentinels. | - -
- -Advanced options - -| Option | Type | Description | -| ----------------- | --------- | ------------------------------------------------------------------------------------ | -| `--command` | `string` | Start at the given command, skip the root and any others | -| `--show-advanced` | `boolean` | Pair with `--help` to show advanced options. | -| `--use-defaults` | `boolean` | Use the oneRepo default configuration. Helpful for generating default documentation. | - -
- -{/* end-auto-generated-from-cli-docgen */} diff --git a/modules/logger/src/LogStep.ts b/modules/logger/src/LogStep.ts index 33618d42..ac650983 100644 --- a/modules/logger/src/LogStep.ts +++ b/modules/logger/src/LogStep.ts @@ -1,21 +1,29 @@ import { Duplex } from 'node:stream'; import pc from 'picocolors'; import { stringify } from './utils/string'; +import type { LineType, LoggedBuffer, Verbosity } from './types'; export type LogStepOptions = { name: string; description?: string; + verbosity: Verbosity; }; export class LogStep extends Duplex { name?: string; - #hasError: boolean = false; #startMark: string; + #verbosity: Verbosity; isPiped: boolean = false; - constructor({ description, name, onEnd }: LogStepOptions) { + #hasError: boolean = false; + #hasWarning: boolean = false; + #hasInfo: boolean = false; + #hasLog: boolean = false; + + constructor({ description, name, verbosity }: LogStepOptions) { super({ decodeStrings: false }); + this.#verbosity = verbosity; this.#startMark = name || `${performance.now()}`; performance.mark(`onerepo_start_${this.#startMark}`, { @@ -46,25 +54,49 @@ export class LogStep extends Duplex { type, contents: stringify(contents), group: this.name, - }), + verbosity: this.#verbosity, + } satisfies LoggedBuffer), ), ); } + set verbosity(verbosity: Verbosity) { + this.#verbosity = verbosity; + } + + get hasError() { + return this.#hasError; + } + + get hasWarning() { + return this.#hasWarning; + } + + get hasInfo() { + return this.#hasInfo; + } + + get hasLog() { + return this.#hasLog; + } + error(contents: unknown) { this.#hasError = true; this.#write('error', contents); } warn(contents: unknown) { + this.#hasWarning = true; this.#write('warn', contents); } info(contents: unknown) { + this.#hasInfo = true; this.#write('info', contents); } log(contents: unknown) { + this.#hasLog = true; this.#write('log', contents); } @@ -108,10 +140,10 @@ export class LogStep extends Duplex { type: 'end', contents: stringify(contents), group: this.name, - }), + hasError: this.#hasError, + verbosity: this.#verbosity, + } satisfies LoggedBuffer), ), ); } } - -export type LineType = 'start' | 'end' | 'error' | 'warn' | 'info' | 'log' | 'debug' | 'timing'; diff --git a/modules/logger/src/Logger.ts b/modules/logger/src/Logger.ts index 92d3ca77..7a4aa5b9 100644 --- a/modules/logger/src/Logger.ts +++ b/modules/logger/src/Logger.ts @@ -4,25 +4,10 @@ import { LogStep } from './LogStep'; import { LogStepToString } from './transforms/LogStepToString'; import { LogProgress } from './transforms/LogProgress'; import { hideCursor, showCursor } from './utils/cursor'; +import type { Verbosity } from './types'; // EventEmitter.defaultMaxListeners = cpus().length + 2; -/** - * Control the verbosity of the log output - * - * | Value | What | Description | - * | ------ | -------------- | ------------------------------------------------ | - * | `<= 0` | Silent | No output will be read or written. | - * | `>= 1` | Error, Info | | - * | `>= 2` | Warnings | | - * | `>= 3` | Log | | - * | `>= 4` | Debug | `logger.debug()` will be included | - * | `>= 5` | Timing | Extra performance timing metrics will be written | - * - * @group Logger - */ -export type Verbosity = 0 | 1 | 2 | 3 | 4 | 5; - /** * @group Logger */ @@ -62,16 +47,17 @@ export class Logger { #hasWarning = false; #hasInfo = false; #hasLog = false; + #captureAll = false; /** * @internal */ constructor(options: LoggerOptions) { - this.verbosity = options.verbosity; + this.#defaultLogger = new LogStep({ name: '', verbosity: options.verbosity }); this.#stream = options.stream ?? process.stderr; this.#captureAll = !!options.captureAll; - this.#defaultLogger = new LogStep({ name: '' }); + this.verbosity = options.verbosity; setCurrent(this); } @@ -97,11 +83,11 @@ export class Logger { this.#verbosity = Math.max(0, value) as Verbosity; if (this.#defaultLogger) { - // this.#defaultLogger.verbosity = this.#verbosity; + this.#defaultLogger.verbosity = this.#verbosity; this.#activate(this.#defaultLogger); } - // this.#steps.forEach((step) => (step.verbosity = this.#verbosity)); + this.#steps.forEach((step) => (step.verbosity = this.#verbosity)); } get writable() { @@ -167,7 +153,7 @@ export class Logger { } /** - * Unpause the logger and resume writing buffered logs to `stderr`. See {@link Logger#pause | `logger.pause()`} for more information. + * Unpause the logger and resume writing buffered logs to the output stream. See {@link Logger#pause | `logger.pause()`} for more information. */ unpause() { this.#stream.uncork(); @@ -185,8 +171,15 @@ export class Logger { * * @param name The name to be written and wrapped around any output logged to this new step. */ - createStep(name: string, { writePrefixes }: { writePrefixes?: boolean } = {}) { - const step = new LogStep({ name }); + createStep( + name: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + opts: { + // @deprecated This option no longer does anything + writePrefixes?: boolean; + } = {}, + ) { + const step = new LogStep({ name, verbosity: this.#verbosity }); this.#steps.push(step); step.on('end', () => this.#onEnd(step)); @@ -213,6 +206,7 @@ export class Logger { * @see {@link LogStep#info | `info()`} This is a pass-through for the main step’s {@link LogStep#info | `info()`} method. */ info(contents: unknown) { + this.#hasInfo = true; this.#defaultLogger.info(contents); } @@ -234,6 +228,7 @@ export class Logger { * @see {@link LogStep#error | `error()`} This is a pass-through for the main step’s {@link LogStep#error | `error()`} method. */ error(contents: unknown) { + this.#hasError = true; this.#defaultLogger.error(contents); } @@ -255,6 +250,7 @@ export class Logger { * @see {@link LogStep#warn | `warn()`} This is a pass-through for the main step’s {@link LogStep#warn | `warn()`} method. */ warn(contents: unknown) { + this.#hasWarning = true; this.#defaultLogger.warn(contents); } @@ -276,6 +272,7 @@ export class Logger { * @see {@link LogStep#log | `log()`} This is a pass-through for the main step’s {@link LogStep#log | `log()`} method. */ log(contents: unknown) { + this.#hasLog = true; this.#defaultLogger.log(contents); } @@ -316,17 +313,41 @@ export class Logger { * @internal */ async end() { - this.pause(); + this.unpause(); + + await new Promise((resolve) => { + setImmediate(() => { + setImmediate(() => { + resolve(); + }); + }); + }); for (const step of this.#steps) { - this.#activate(step); - step.warn( - `Step "${step.name}" did not finish before command shutdown. Fix this issue by updating this command to \`await step.end();\` at the appropriate time.`, + this.#defaultLogger.warn( + `Step "${step.name}" did not finish before command shutdown. Fix this issue by updating this command to call \`step.end();\` at the appropriate time.`, ); - step.end(); + await this.#onEnd(step); } - await this.#defaultLogger.end(); + await new Promise((resolve) => { + setImmediate(() => { + setImmediate(() => { + resolve(); + }); + }); + }); + + this.#defaultLogger.end(); + + await new Promise((resolve) => { + setImmediate(() => { + setImmediate(() => { + resolve(); + }); + }); + }); + destroyCurrent(); showCursor(); } @@ -335,7 +356,6 @@ export class Logger { const activeStep = this.#steps.find((step) => step.isPiped); if (activeStep) { - // console.log('cannot activate', step.name, 'because', activeStep.name); return; } @@ -348,15 +368,12 @@ export class Logger { // step.unpipe(); } - this.unpause(); + // this.unpause(); - if (!step.name) { - step.pipe(new LogStepToString({ verbosity: this.#verbosity })).pipe(this.#stream); + if (!step.name || !(this.#stream as typeof process.stderr).isTTY) { + step.pipe(new LogStepToString()).pipe(this.#stream); } else { - step - .pipe(new LogStepToString({ verbosity: this.#verbosity })) - .pipe(new LogProgress()) - .pipe(this.#stream); + step.pipe(new LogStepToString()).pipe(new LogProgress()).pipe(this.#stream); } step.isPiped = true; } @@ -371,13 +388,15 @@ export class Logger { return; } + this.#setState(step); + step.unpipe(); step.destroy(); step.isPiped = false; // step.destroy(); // await step.flush(); - // this.#defaultLogger.resume(); + this.#defaultLogger.resume(); // if (step.hasError && process.env.GITHUB_RUN_ID) { // this.error('The previous step has errors.'); @@ -386,39 +405,26 @@ export class Logger { // Remove this step this.#steps.splice(index, 1); - // await new Promise((resolve) => { - // setImmediate(() => { - // setImmediate(() => { - this.#defaultLogger.pause(); - // resolve(); - // }); - // }); - // }); - if (this.#steps.length < 1) { return; } + await new Promise((resolve) => { + setImmediate(() => { + setImmediate(() => { + this.#defaultLogger.pause(); + resolve(); + }); + }); + }); + this.#activate(this.#steps[0]); }; - // #onMessage = (type: 'error' | 'warn' | 'info' | 'log' | 'debug') => { - // switch (type) { - // case 'error': - // this.#hasError = true; - // this.#defaultLogger.hasError = true; - // break; - // case 'warn': - // this.#hasWarning = true; - // break; - // case 'info': - // this.#hasInfo = true; - // break; - // case 'log': - // this.#hasLog = true; - // break; - // default: - // // no default - // } - // }; + #setState = (step: LogStep) => { + this.#hasError = this.#hasError || step.hasError; + this.#hasWarning = this.#hasWarning || step.hasWarning; + this.#hasInfo = this.#hasInfo || step.hasInfo; + this.#hasLog = this.#hasLog || step.hasLog; + }; } diff --git a/modules/logger/src/__tests__/Logger.test.ts b/modules/logger/src/__tests__/Logger.test.ts index b7213417..14876a83 100644 --- a/modules/logger/src/__tests__/Logger.test.ts +++ b/modules/logger/src/__tests__/Logger.test.ts @@ -74,7 +74,7 @@ describe('Logger', () => { }, ); - test('logs "completed" message', async () => { + test.only('logs "completed" message', async () => { const stream = new PassThrough(); let out = ''; stream.on('data', (chunk) => { @@ -84,9 +84,17 @@ describe('Logger', () => { const logger = new Logger({ verbosity: 2, stream }); const step = logger.createStep('tacos'); - await step.end(); + step.end(); await logger.end(); + await new Promise((resolve) => { + setTimeout(() => { + setImmediate(() => { + resolve(); + }); + }, 100); + }); + expect(out).toEqual('foo'); expect(out).toMatch(`${pc.dim(pc.bold('■'))} ${pc.green('✔')} Completed`); }); diff --git a/modules/logger/src/index.ts b/modules/logger/src/index.ts index cb740251..ef31c382 100644 --- a/modules/logger/src/index.ts +++ b/modules/logger/src/index.ts @@ -1,13 +1,14 @@ -import { PassThrough } from 'node:stream'; +import { Transform } from 'node:stream'; import restoreCursorDefault from 'restore-cursor'; import { Logger } from './Logger'; // import { LogStep } from './LogStep'; import { destroyCurrent, getCurrent, setCurrent } from './global'; import type { LogStep } from './LogStep'; +import { prefix } from './transforms/LogStepToString'; export * from './Logger'; -// export * from './LogStep'; export * from './LogStep'; +export * from './types'; /** * This gets the logger singleton for use across all of oneRepo and its commands. @@ -97,7 +98,7 @@ export async function stepWrapper( export function bufferSubLogger(step: LogStep): { logger: Logger; end: () => Promise } { const logger = getLogger(); // const buffer = new LogStep({ name: '' }); - const stream = new PassThrough(); + const stream = new Buffered(); const subLogger = new Logger({ verbosity: logger.verbosity, stream, captureAll: true }); // let activeStep: Buffer | undefined; // function write(method: 'error' | 'info' | 'warn' | 'log' | 'debug', chunk: Buffer) { @@ -135,15 +136,30 @@ export function bufferSubLogger(step: LogStep): { logger: Logger; end: () => Pro async end() { // buffer.unpipe(); // buffer.off('data', proxyChunks); - // await new Promise((resolve) => { - // setImmediate(async () => { - // await subLogger.end(); - // buffer.destroy(); - // resolve(); - // }); - // }); + await new Promise((resolve) => { + setImmediate(async () => { + stream.unpipe(); + stream.destroy(); + // await subLogger.end(); + // buffer.destroy(); + resolve(); + }); + }); }, }; } export const restoreCursor = restoreCursorDefault; + +class Buffered extends Transform { + _transform(chunk: Buffer, encoding = 'utf8', callback: () => void) { + this.push( + chunk + .toString() + .split('\n') + .map((line) => ` │${line}`) + .join('\n'), + ); + callback(); + } +} diff --git a/modules/logger/src/transforms/LogStepToString.ts b/modules/logger/src/transforms/LogStepToString.ts index 53a373ce..754e159d 100644 --- a/modules/logger/src/transforms/LogStepToString.ts +++ b/modules/logger/src/transforms/LogStepToString.ts @@ -1,18 +1,11 @@ import { Transform } from 'node:stream'; import pc from 'picocolors'; -import type { LineType } from '../LogStep'; +import type { LineType, LoggedBuffer } from '../types'; import { ensureNewline, stringify } from '../utils/string'; -export type StepToStringOptions = { - verbosity: number; -}; - export class LogStepToString extends Transform { - #verbosity: number; - - constructor({ verbosity }: StepToStringOptions) { + constructor() { super({ decodeStrings: false }); - this.#verbosity = verbosity; } _transform( @@ -22,10 +15,9 @@ export class LogStepToString extends Transform { callback: () => void, ) { try { - const data = JSON.parse(chunk.toString()) as { type: LineType; contents: string; group?: string }; - // console.log(chunk.toString()); - if (typeMinVerbosity[data.type] <= this.#verbosity) { - this.push(ensureNewline(`${this.#prefix(data.type, data.group, stringify(data.contents))}`)); + const data = JSON.parse(chunk.toString()) as LoggedBuffer; + if (typeMinVerbosity[data.type] <= data.verbosity) { + this.push(ensureNewline(`${this.#prefix(data.type, data.group, stringify(data.contents), data.hasError)}`)); } } catch (e) { this.push(chunk); @@ -38,12 +30,12 @@ export class LogStepToString extends Transform { callback(); } - #prefix(type: LineType, group: string | undefined, output: string) { + #prefix(type: LineType, group: string | undefined, output: string, hasError?: boolean) { if (type === 'end') { - return `${!group ? '◼︎ ' : prefix[type]}${output}`; + return `${!group ? pc.bold(pc.dim('◼︎ ')) : prefix[type]}${hasError ? pc.red('✘') : pc.green('✔')} ${output}`; } if (type === 'start') { - return `${!group ? '➤ ' : prefix[type]}${output}`; + return `${!group ? pc.bold(pc.dim('➤ ')) : prefix[type]}${output}`; } return output .split('\n') @@ -63,8 +55,6 @@ const typeMinVerbosity: Record = { }; export const prefix: Record = { - // FAIL: pc.red('✘'), - // SUCCESS: pc.green('✔'), timing: pc.red('⏳'), start: ' ┌ ', end: ' └ ', diff --git a/modules/logger/src/types.ts b/modules/logger/src/types.ts new file mode 100644 index 00000000..d01e8d21 --- /dev/null +++ b/modules/logger/src/types.ts @@ -0,0 +1,25 @@ +/** + * Control the verbosity of the log output + * + * | Value | What | Description | + * | ------ | -------------- | ------------------------------------------------ | + * | `<= 0` | Silent | No output will be read or written. | + * | `>= 1` | Error, Info | | + * | `>= 2` | Warnings | | + * | `>= 3` | Log | | + * | `>= 4` | Debug | `logger.debug()` will be included | + * | `>= 5` | Timing | Extra performance timing metrics will be written | + * + * @group Logger + */ +export type Verbosity = 0 | 1 | 2 | 3 | 4 | 5; + +export type LineType = 'start' | 'end' | 'error' | 'warn' | 'info' | 'log' | 'debug' | 'timing'; + +export type LoggedBuffer = { + type: LineType; + contents: string; + group?: string; + hasError?: boolean; + verbosity: Verbosity; +}; diff --git a/modules/onerepo/src/core/graph/__tests__/verify.test.ts b/modules/onerepo/src/core/graph/__tests__/verify.test.ts index 61b82aeb..f1699fc4 100644 --- a/modules/onerepo/src/core/graph/__tests__/verify.test.ts +++ b/modules/onerepo/src/core/graph/__tests__/verify.test.ts @@ -38,7 +38,7 @@ describe('verify', () => { const graph = getGraph(path.join(__dirname, '__fixtures__', 'repo')); const schema = require.resolve('./__fixtures__/tsconfig-schema.ts'); - await expect(run(`--custom-schema ${schema}`, { graph })).rejects.toMatch('must be equal to constant'); + await expect(run(`--custom-schema ${schema}`, { graph })).rejects.toEqual('must be equal to constant'); }); test('can verify js (eg jest.config, etc)', async () => { diff --git a/modules/onerepo/src/core/graph/verify.ts b/modules/onerepo/src/core/graph/verify.ts index 0ec73e3a..ac3f239d 100644 --- a/modules/onerepo/src/core/graph/verify.ts +++ b/modules/onerepo/src/core/graph/verify.ts @@ -9,6 +9,7 @@ import type { AnySchema } from 'ajv'; import ajvErrors from 'ajv-errors'; import type { Builder, Handler } from '@onerepo/yargs'; import type { Graph, Workspace } from '@onerepo/graph'; +import type { LogStep } from '@onerepo/logger'; import { verifyDependencies } from '../dependencies/utils/verify-dependencies'; import { epilogue } from '../dependencies/verify'; import { defaultValidators } from './schema'; @@ -87,7 +88,7 @@ export const handler: Handler = async function handler(argv, { graph, logg if (required && files.length === 0) { const msg = `❓ Missing required file matching pattern "${schemaKey.split(splitChar)[1]}"`; schemaStep.error(msg); - writeGithubError(msg); + writeGithubError(schemaStep, msg); } for (const file of files) { if (!(file in map)) { @@ -119,12 +120,12 @@ export const handler: Handler = async function handler(argv, { graph, logg schemaStep.error(`Errors in ${workspace.resolve(file)}:`); ajv.errors?.forEach((err) => { schemaStep.error(` ↳ ${err.message}`); - writeGithubError(err.message, graph.root.relative(workspace.resolve(file))); + writeGithubError(schemaStep, err.message, graph.root.relative(workspace.resolve(file))); }); } } } - await schemaStep.end(); + schemaStep.end(); } }; @@ -146,8 +147,8 @@ const splitChar = '\u200b'; type ExtendedSchema = AnySchema & { $required?: boolean }; -function writeGithubError(message: string | undefined, filename?: string) { +function writeGithubError(step: LogStep, message: string | undefined, filename?: string) { if (process.env.GITHUB_RUN_ID) { - process.stderr.write(`::error${filename ? ` file=${filename}` : ''}::${message}\n`); + step.write(`::error${filename ? ` file=${filename}` : ''}::${message}\n`); } } diff --git a/modules/onerepo/src/setup/setup.ts b/modules/onerepo/src/setup/setup.ts index ae0f8653..f179c102 100644 --- a/modules/onerepo/src/setup/setup.ts +++ b/modules/onerepo/src/setup/setup.ts @@ -9,6 +9,7 @@ import { globSync } from 'glob'; import { commandDirOptions, setupYargs } from '@onerepo/yargs'; import type { Graph } from '@onerepo/graph'; import { getGraph } from '@onerepo/graph'; +import type { Verbosity } from '@onerepo/logger'; import { Logger, getLogger } from '@onerepo/logger'; import type { RequireDirectoryOptions, Argv as Yargv } from 'yargs'; import type { Argv, DefaultArgv, Yargs } from '@onerepo/yargs'; @@ -195,24 +196,13 @@ export async function setup({ }); const logger = getLogger(); - - // allow the last performance mark to propagate to observers. Super hacky. - await new Promise((resolve) => { - setImmediate(() => { - resolve(); - }); - }); - + // Enforce the initial verbosity, in case it was modified + logger.verbosity = argv.verbosity as Verbosity; await logger.end(); // Register a new logger on the top of the stack to silence output so that shutdown handlers to not write any output const silencedLogger = new Logger({ verbosity: 0 }); await shutdown(argv); - await new Promise((resolve) => { - setImmediate(() => { - resolve(); - }); - }); await silencedLogger.end(); }, }; diff --git a/modules/test-cli/src/index.ts b/modules/test-cli/src/index.ts index 620ffc08..7b82f306 100644 --- a/modules/test-cli/src/index.ts +++ b/modules/test-cli/src/index.ts @@ -100,6 +100,12 @@ async function runHandler>( }); const logger = new Logger({ verbosity: 5, stream }); + await new Promise((resolve) => { + setImmediate(() => { + resolve(); + }); + }); + const { builderExtras, graph = getGraph(path.join(dirname, 'fixtures', 'repo')) } = extras; const argv = await runBuilder(builder, cmd, graph, builderExtras); @@ -109,7 +115,6 @@ async function runHandler>( const wrappedGetFilepaths = (opts?: Parameters[2]) => builders.getFilepaths(graph, argv as builders.Argv, opts); - const error: unknown = undefined; await handler(argv, { logger, getAffected: wrappedGetAffected, @@ -121,8 +126,14 @@ async function runHandler>( await logger.end(); - if (logger.hasError || error) { - return Promise.reject(out || error); + await new Promise((resolve) => { + setImmediate(() => { + resolve(); + }); + }); + + if (logger.hasError) { + return Promise.reject(out); } return out; From bc8910b3078df9515fcd6be2cfc1d96813dba85f Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Sun, 7 Apr 2024 15:21:33 -0700 Subject: [PATCH 07/17] fix: exit code --- modules/logger/src/LogStep.ts | 16 ++++++++++++++++ modules/logger/src/Logger.ts | 16 ++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/modules/logger/src/LogStep.ts b/modules/logger/src/LogStep.ts index ac650983..a64d21b8 100644 --- a/modules/logger/src/LogStep.ts +++ b/modules/logger/src/LogStep.ts @@ -64,18 +64,34 @@ export class LogStep extends Duplex { this.#verbosity = verbosity; } + set hasError(has: boolean) { + this.#hasError = this.#hasError || has; + } + get hasError() { return this.#hasError; } + set hasWarning(has: boolean) { + this.#hasWarning = this.#hasWarning || has; + } + get hasWarning() { return this.#hasWarning; } + set hasInfo(has: boolean) { + this.#hasInfo = this.#hasInfo || has; + } + get hasInfo() { return this.#hasInfo; } + set hasLog(has: boolean) { + this.#hasLog = this.#hasLog || has; + } + get hasLog() { return this.#hasLog; } diff --git a/modules/logger/src/Logger.ts b/modules/logger/src/Logger.ts index 7a4aa5b9..a5b42878 100644 --- a/modules/logger/src/Logger.ts +++ b/modules/logger/src/Logger.ts @@ -98,28 +98,28 @@ export class Logger { * Whether or not an error has been sent to the logger or any of its steps. This is not necessarily indicative of uncaught thrown errors, but solely on whether `.error()` has been called in the `Logger` or any `Step` instance. */ get hasError() { - return this.#hasError; + return this.#defaultLogger.hasError; } /** * Whether or not a warning has been sent to the logger or any of its steps. */ get hasWarning() { - return this.#hasWarning; + return this.#defaultLogger.hasWarning; } /** * Whether or not an info message has been sent to the logger or any of its steps. */ get hasInfo() { - return this.#hasInfo; + return this.#defaultLogger.hasInfo; } /** * Whether or not a log message has been sent to the logger or any of its steps. */ get hasLog() { - return this.#hasLog; + return this.#defaultLogger.hasLog; } /** @@ -422,9 +422,9 @@ export class Logger { }; #setState = (step: LogStep) => { - this.#hasError = this.#hasError || step.hasError; - this.#hasWarning = this.#hasWarning || step.hasWarning; - this.#hasInfo = this.#hasInfo || step.hasInfo; - this.#hasLog = this.#hasLog || step.hasLog; + this.#defaultLogger.hasError = this.#defaultLogger.hasError || step.hasError; + this.#defaultLogger.hasWarning = this.#defaultLogger.hasWarning || step.hasWarning; + this.#defaultLogger.hasInfo = this.#defaultLogger.hasInfo || step.hasInfo; + this.#defaultLogger.hasLog = this.#defaultLogger.hasLog || step.hasLog; }; } From 220397f10a7a86c2ba7b75106721c1bb18c5c57d Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Sun, 7 Apr 2024 15:22:52 -0700 Subject: [PATCH 08/17] revert unintentional docs change --- docs/src/content/docs/plugins/docgen.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/content/docs/plugins/docgen.mdx b/docs/src/content/docs/plugins/docgen.mdx index a909396a..09d87bc2 100644 --- a/docs/src/content/docs/plugins/docgen.mdx +++ b/docs/src/content/docs/plugins/docgen.mdx @@ -1,3 +1,5 @@ +<<<<<<< HEAD + ## <<<<<<< HEAD title: '@onerepo/plugin-docgen' From 7671f651f43625a1db1da23deca05a2badffb28b Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Sun, 7 Apr 2024 15:25:54 -0700 Subject: [PATCH 09/17] fix: exit code --- modules/onerepo/src/setup/setup.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/onerepo/src/setup/setup.ts b/modules/onerepo/src/setup/setup.ts index f179c102..e67395a9 100644 --- a/modules/onerepo/src/setup/setup.ts +++ b/modules/onerepo/src/setup/setup.ts @@ -200,6 +200,10 @@ export async function setup({ logger.verbosity = argv.verbosity as Verbosity; await logger.end(); + if (logger.hasError) { + process.exitCode = 1; + } + // Register a new logger on the top of the stack to silence output so that shutdown handlers to not write any output const silencedLogger = new Logger({ verbosity: 0 }); await shutdown(argv); From 33054acad3909bc4694c0e4f79f9a58dfbed1a9d Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Sun, 7 Apr 2024 18:00:24 -0700 Subject: [PATCH 10/17] docs(logger): fill out some docs, exclude duplex/externals --- docs/commands/typedoc.ts | 1 + docs/src/content/docs/api/index.md | 7753 ++++++---------------------- modules/logger/src/LogStep.ts | 257 +- modules/logger/src/Logger.ts | 76 +- modules/logger/src/types.ts | 6 + 5 files changed, 1939 insertions(+), 6154 deletions(-) diff --git a/docs/commands/typedoc.ts b/docs/commands/typedoc.ts index 196a1a08..41044d23 100644 --- a/docs/commands/typedoc.ts +++ b/docs/commands/typedoc.ts @@ -52,6 +52,7 @@ export const handler: Handler = async (argv, { graph, logger }) => { 'typedoc-plugin-markdown', '--useCodeBlocks', 'true', + '--excludeExternals', '--entryFileName', 'index.md', '--options', diff --git a/docs/src/content/docs/api/index.md b/docs/src/content/docs/api/index.md index ce71725b..7ed12c25 100644 --- a/docs/src/content/docs/api/index.md +++ b/docs/src/content/docs/api/index.md @@ -4,6891 +4,2445 @@ description: Full API documentation for oneRepo. --- - + -## Classes +## Variables -### LogStep +### defaultConfig -#### Extends +```ts +const defaultConfig: Required; +``` -- `Duplex` +**Defined in:** [modules/onerepo/src/setup/setup.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/setup/setup.ts) -#### Constructors +## Functions -##### new LogStep() +### restoreCursor() ```ts -new LogStep(__namedParameters): LogStep +function restoreCursor(): void; ``` -**Parameters:** - -| Parameter | Type | -| ------------------- | ----------------------------------- | -| `__namedParameters` | [`LogStepOptions`](#logstepoptions) | +Gracefully restore the CLI cursor on exit. -**Returns:** [`LogStep`](#logstep) +Prevent the cursor you have hidden interactively from remaining hidden if the process crashes. -###### Overrides +It does nothing if run in a non-TTY context. -`Duplex.constructor` +**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) +#### Example -#### Properties +``` +import restoreCursor from 'restore-cursor'; -| Property | Modifier | Type | Description | Inherited from | Defined in | -| ------------------------ | ---------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -| `allowHalfOpen` | `public` | `boolean` | If `false` then the stream will automatically end the writable side when the readable side ends. Set initially by the `allowHalfOpen` constructor option, which defaults to `true`. This can be changed manually to change the half-open behavior of an existing `Duplex` stream instance, but must be changed before the `'end'` event is emitted. **Since** v0.9.4 | `Duplex.allowHalfOpen` | node_modules/@types/node/stream.d.ts:1076 | -| `captureRejections` | `static` | `boolean` | Value: [boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) Change the default `captureRejections` option on all new `EventEmitter` objects. **Since** v13.4.0, v12.16.0 | `Duplex.captureRejections` | node_modules/@types/node/events.d.ts:459 | -| `captureRejectionSymbol` | `readonly` | _typeof_ `captureRejectionSymbol` | Value: `Symbol.for('nodejs.rejection')` See how to write a custom `rejection handler`. **Since** v13.4.0, v12.16.0 | `Duplex.captureRejectionSymbol` | node_modules/@types/node/events.d.ts:452 | -| `closed` | `readonly` | `boolean` | Is `true` after `'close'` has been emitted. **Since** v18.0.0 | `Duplex.closed` | node_modules/@types/node/stream.d.ts:1065 | -| `defaultMaxListeners` | `static` | `number` | By default, a maximum of `10` listeners can be registered for any single event. This limit can be changed for individual `EventEmitter` instances using the `emitter.setMaxListeners(n)` method. To change the default for _all_`EventEmitter` instances, the `events.defaultMaxListeners` property can be used. If this value is not a positive number, a `RangeError` is thrown. Take caution when setting the `events.defaultMaxListeners` because the change affects _all_ `EventEmitter` instances, including those created before the change is made. However, calling `emitter.setMaxListeners(n)` still has precedence over `events.defaultMaxListeners`. This is not a hard limit. The `EventEmitter` instance will allow more listeners to be added but will output a trace warning to stderr indicating that a "possible EventEmitter memory leak" has been detected. For any single `EventEmitter`, the `emitter.getMaxListeners()` and `emitter.setMaxListeners()` methods can be used to temporarily avoid this warning: `import { EventEmitter } from 'node:events'; const emitter = new EventEmitter(); emitter.setMaxListeners(emitter.getMaxListeners() + 1); emitter.once('event', () => { // do stuff emitter.setMaxListeners(Math.max(emitter.getMaxListeners() - 1, 0)); });` The `--trace-warnings` command-line flag can be used to display the stack trace for such warnings. The emitted warning can be inspected with `process.on('warning')` and will have the additional `emitter`, `type`, and `count` properties, referring to the event emitter instance, the event's name and the number of attached listeners, respectively. Its `name` property is set to `'MaxListenersExceededWarning'`. **Since** v0.11.2 | `Duplex.defaultMaxListeners` | node_modules/@types/node/events.d.ts:498 | -| `destroyed` | `public` | `boolean` | Is `true` after `readable.destroy()` has been called. **Since** v8.0.0 | `Duplex.destroyed` | node_modules/@types/node/stream.d.ts:121 | -| `errored` | `readonly` | `null` \| `Error` | Returns error if the stream has been destroyed with an error. **Since** v18.0.0 | `Duplex.errored` | node_modules/@types/node/stream.d.ts:1066 | -| `errorMonitor` | `readonly` | _typeof_ `errorMonitor` | This symbol shall be used to install a listener for only monitoring `'error'` events. Listeners installed using this symbol are called before the regular `'error'` listeners are called. Installing a listener using this symbol does not change the behavior once an `'error'` event is emitted. Therefore, the process will still crash if no regular `'error'` listener is installed. **Since** v13.6.0, v12.17.0 | `Duplex.errorMonitor` | node_modules/@types/node/events.d.ts:445 | -| `isPiped` | `public` | `boolean` | - | - | [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) | -| `name?` | `public` | `string` | - | - | [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) | -| `readable` | `public` | `boolean` | Is `true` if it is safe to call [read](#read), which means the stream has not been destroyed or emitted `'error'` or `'end'`. **Since** v11.4.0 | `Duplex.readable` | node_modules/@types/node/stream.d.ts:77 | -| `readableAborted` | `readonly` | `boolean` | **Experimental** Returns whether the stream was destroyed or errored before emitting `'end'`. **Since** v16.8.0 | `Duplex.readableAborted` | node_modules/@types/node/stream.d.ts:71 | -| `readableDidRead` | `readonly` | `boolean` | **Experimental** Returns whether `'data'` has been emitted. **Since** v16.7.0, v14.18.0 | `Duplex.readableDidRead` | node_modules/@types/node/stream.d.ts:83 | -| `readableEncoding` | `readonly` | `null` \| `BufferEncoding` | Getter for the property `encoding` of a given `Readable` stream. The `encoding` property can be set using the [setEncoding](#setencoding) method. **Since** v12.7.0 | `Duplex.readableEncoding` | node_modules/@types/node/stream.d.ts:88 | -| `readableEnded` | `readonly` | `boolean` | Becomes `true` when [`'end'`](https://nodejs.org/docs/latest-v20.x/api/stream.html#event-end) event is emitted. **Since** v12.9.0 | `Duplex.readableEnded` | node_modules/@types/node/stream.d.ts:93 | -| `readableFlowing` | `readonly` | `null` \| `boolean` | This property reflects the current state of a `Readable` stream as described in the [Three states](https://nodejs.org/docs/latest-v20.x/api/stream.html#three-states) section. **Since** v9.4.0 | `Duplex.readableFlowing` | node_modules/@types/node/stream.d.ts:99 | -| `readableHighWaterMark` | `readonly` | `number` | Returns the value of `highWaterMark` passed when creating this `Readable`. **Since** v9.3.0 | `Duplex.readableHighWaterMark` | node_modules/@types/node/stream.d.ts:104 | -| `readableLength` | `readonly` | `number` | This property contains the number of bytes (or objects) in the queue ready to be read. The value provides introspection data regarding the status of the `highWaterMark`. **Since** v9.4.0 | `Duplex.readableLength` | node_modules/@types/node/stream.d.ts:111 | -| `readableObjectMode` | `readonly` | `boolean` | Getter for the property `objectMode` of a given `Readable` stream. **Since** v12.3.0 | `Duplex.readableObjectMode` | node_modules/@types/node/stream.d.ts:116 | -| `writable` | `readonly` | `boolean` | Is `true` if it is safe to call `writable.write()`, which means the stream has not been destroyed, errored, or ended. **Since** v11.4.0 | `Duplex.writable` | node_modules/@types/node/stream.d.ts:1057 | -| `writableCorked` | `readonly` | `number` | Number of times `writable.uncork()` needs to be called in order to fully uncork the stream. **Since** v13.2.0, v12.16.0 | `Duplex.writableCorked` | node_modules/@types/node/stream.d.ts:1063 | -| `writableEnded` | `readonly` | `boolean` | Is `true` after `writable.end()` has been called. This property does not indicate whether the data has been flushed, for this use `writable.writableFinished` instead. **Since** v12.9.0 | `Duplex.writableEnded` | node_modules/@types/node/stream.d.ts:1058 | -| `writableFinished` | `readonly` | `boolean` | Is set to `true` immediately before the `'finish'` event is emitted. **Since** v12.6.0 | `Duplex.writableFinished` | node_modules/@types/node/stream.d.ts:1059 | -| `writableHighWaterMark` | `readonly` | `number` | Return the value of `highWaterMark` passed when creating this `Writable`. **Since** v9.3.0 | `Duplex.writableHighWaterMark` | node_modules/@types/node/stream.d.ts:1060 | -| `writableLength` | `readonly` | `number` | This property contains the number of bytes (or objects) in the queue ready to be written. The value provides introspection data regarding the status of the `highWaterMark`. **Since** v9.4.0 | `Duplex.writableLength` | node_modules/@types/node/stream.d.ts:1061 | -| `writableNeedDrain` | `readonly` | `boolean` | Is `true` if the stream's buffer has been full and stream will emit `'drain'`. **Since** v15.2.0, v14.17.0 | `Duplex.writableNeedDrain` | node_modules/@types/node/stream.d.ts:1064 | -| `writableObjectMode` | `readonly` | `boolean` | Getter for the property `objectMode` of a given `Writable` stream. **Since** v12.3.0 | `Duplex.writableObjectMode` | node_modules/@types/node/stream.d.ts:1062 | +restoreCursor(); +``` -#### Accessors +**Defined in:** [modules/logger/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/index.ts) -##### hasError +## Commands -###### Get Signature +### Argv\ ```ts -get hasError(): boolean +type Argv: Arguments; ``` -**Returns:** `boolean` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -##### hasInfo +Helper for combining local parsed arguments along with the default arguments provided by the oneRepo command module. -###### Get Signature +#### Type Parameters -```ts -get hasInfo(): boolean -``` +| Type Parameter | Default type | Description | +| -------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `CommandArgv` | `object` | Arguments that will be parsed for this command, always a union with [\`DefaultArgv\`](#defaultargv) and [\`PositionalArgv\`](#positionalargv). | -**Returns:** `boolean` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) +**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) -##### hasLog +--- -###### Get Signature +### Builder()\ ```ts -get hasLog(): boolean +type Builder: (yargs) => Yargv; ``` -**Returns:** `boolean` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -##### hasWarning +Option argument parser for the given command. See [Yargs `.command(module)`](http://yargs.js.org/docs/#api-reference-commandmodule) for more, but note that only the object variant is not accepted – only function variants will be accepted in oneRepo commands. -###### Get Signature +For common arguments that work in conjunction with [\`HandlerExtra\`](#handlerextra) methods like `getAffected()`, you can use helpers from the [\`builders\` namespace](namespaces/builders/), like [\`builders.withAffected()\`](namespaces/builders/#withaffected). ```ts -get hasWarning(): boolean +type Argv = { + 'with-tacos'?: boolean; +}; + +export const builder: Builder = (yargs) => + yargs.usage(`$0 ${command}`).option('with-tacos', { + description: 'Include tacos', + type: 'boolean', + }); ``` -**Returns:** `boolean` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) +#### Type Parameters -##### verbosity +| Type Parameter | Default type | Description | +| -------------- | ------------ | ---------------------------------------------- | +| `CommandArgv` | `object` | Arguments that will be parsed for this command | -###### Set Signature +**Parameters:** -```ts -set verbosity(verbosity): void -``` +| Parameter | Type | Description | +| --------- | ------- | --------------------------------------------------------------------------------------------------------- | +| `yargs` | `Yargs` | The Yargs instance. See [Yargs `.command(module)`](http://yargs.js.org/docs/#api-reference-commandmodule) | -**Parameters:** +**Returns:** `Yargv`\<`CommandArgv`\> +**See also:** -| Parameter | Type | -| ----------- | --------------------------- | -| `verbosity` | [`Verbosity`](#verbosity-2) | +- [Yargs `.command(module)`](http://yargs.js.org/docs/#api-reference-commandmodule) for general usage. +- Common extensions via the [\`builders\`](namespaces/builders/) namespace. -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) +**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) -#### Methods +--- -##### \_construct()? +### DefaultArgv ```ts -optional _construct(callback): void +type DefaultArgv: { + dry-run: boolean; + quiet: boolean; + skip-engine-check: boolean; + verbosity: number; +}; ``` -**Parameters:** - -| Parameter | Type | -| ---------- | -------------------- | -| `callback` | (`error`?) => `void` | - -**Returns:** `void` - -###### Inherited from - -`Duplex._construct` +Default arguments provided globally for all commands. These arguments are included by when using [\`Builder\`](#buildercommandargv) and [\`Handler\`](#handlercommandargv). -**Defined in:** node_modules/@types/node/stream.d.ts:133 +#### Type declaration -##### \_destroy() +##### dry-run ```ts -_destroy(error, callback): void +dry-run: boolean; ``` -**Parameters:** - -| Parameter | Type | -| ---------- | -------------------- | -| `error` | `null` \| `Error` | -| `callback` | (`error`?) => `void` | - -**Returns:** `void` - -###### Inherited from +Whether the command should run non-destructive dry-mode. This prevents all subprocesses, files, and git operations from running unless explicitly specified as safe to run. -`Duplex._destroy` +Also internally sets `process.env.ONEREPO_DRY_RUN = 'true'`. -**Defined in:** node_modules/@types/node/stream.d.ts:1119 +**Default:** `false` -##### \_final() +##### quiet ```ts -_final(callback): void +quiet: boolean; ``` -**Parameters:** +Silence all logger output. Prevents _all_ stdout and stderr output from the logger entirely. -| Parameter | Type | -| ---------- | ------------ | -| `callback` | () => `void` | +**Default:** `false` -**Returns:** `void` +##### skip-engine-check -###### Overrides +```ts +skip-engine-check: boolean; +``` -`Duplex._final` +Skip the engines check. When `false`, oneRepo will the current process's node version with the range for `engines.node` as defined in `package.json`. If not defined in the root `package.json`, this will be skipped. -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) +**Default:** `false` -##### \_read() +##### verbosity ```ts -_read(): void +verbosity: number; ``` -**Returns:** `void` +Verbosity level for the Logger. See Logger.verbosity for more information. -###### Overrides +**Default:** `3` +**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) -`Duplex._read` +--- -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) +### Handler()\ + +```ts +type Handler: (argv, extra) => Promise; +``` -##### \_write() +Command handler that includes oneRepo tools like `graph`, `logger`, and more. This function is type-safe if `Argv` is correctly passed through to the type definition. ```ts -_write( - chunk, - encoding, - callback): void +type Argv = { + 'with-tacos'?: boolean; +}; +export const handler: Handler = (argv, { logger }) => { + const { 'with-tacos': withTacos, '--': passthrough } = argv; + logger.log(withTacos ? 'Include tacos' : 'No tacos, thanks'); + logger.debug(passthrough); +}; ``` +#### Type Parameters + +| Type Parameter | Default type | Description | +| -------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `CommandArgv` | `object` | Arguments that will be parsed for this command. DefaultArguments will be automatically merged into this object for use within the handler. | + **Parameters:** -| Parameter | Type | -| ---------- | ----------------------------------------- | -| `chunk` | `string` \| `Buffer`\<`ArrayBufferLike`\> | -| `encoding` | `undefined` \| `string` | -| `callback` | () => `void` | +| Parameter | Type | +| --------- | ------------------------------------------- | +| `argv` | [`Argv`](#argvcommandargv)\<`CommandArgv`\> | +| `extra` | [`HandlerExtra`](#handlerextra) | -**Returns:** `void` +**Returns:** `Promise`\<`void`\> +**See also:** -###### Overrides +- [Yargs `.command(module)`](http://yargs.js.org/docs/#api-reference-commandmodule) for general usage. +- [\`HandlerExtra\`](#handlerextra) for extended extra arguments provided above and beyond the scope of Yargs. -`Duplex._write` +**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) +--- -##### \_writev()? +### HandlerExtra ```ts -optional _writev(chunks, callback): void +type HandlerExtra: { + config: Required; + getAffected: (opts?) => Promise; + getFilepaths: (opts?) => Promise; + getWorkspaces: (opts?) => Promise; + graph: Graph; + logger: Logger; +}; ``` -**Parameters:** +Commands in oneRepo extend beyond what Yargs is able to provide by adding a second argument to the handler. -| Parameter | Type | -| ---------- | ----------------------------------------------------- | -| `chunks` | \{ `chunk`: `any`; `encoding`: `BufferEncoding`; \}[] | -| `callback` | (`error`?) => `void` | +All extras are available as the second argument on your [\`Handler\`](#handlercommandargv) -**Returns:** `void` +```ts +export const handler: Handler = (argv, { getAffected, getFilepaths, getWorkspace, logger }) => { + logger.warn('Nothing to do!'); +}; +``` -###### Inherited from +Overriding the affected threshold in `getFilepaths` -`Duplex._writev` +```ts +export const handler: Handler = (argv, { getFilepaths }) => { + const filepaths = await getFilepaths({ affectedThreshold: 0 }); +}; +``` -**Defined in:** node_modules/@types/node/stream.d.ts:1112 +#### Type declaration -##### \[asyncDispose\]() +##### config ```ts -asyncDispose: Promise; +config: Required; ``` -Calls `readable.destroy()` with an `AbortError` and returns a promise that fulfills when the stream is finished. +This repository’s oneRepo [config](#rootconfigcustomlifecycles), resolved with all defaults. -**Returns:** `Promise`\<`void`\> +##### getAffected() -###### Since +```ts +getAffected: (opts?) => Promise; +``` + +Get the affected Workspaces based on the current state of the repository. -v20.4.0 +This is a wrapped implementation of [\`builders.getAffected\`](namespaces/builders/#getaffected) that does not require passing the `graph` argument. -###### Inherited from +**Parameters:** -`Duplex.[asyncDispose]` +| Parameter | Type | +| --------- | ----------------------------------------------------- | +| `opts`? | [`GetterOptions`](namespaces/builders/#getteroptions) | -**Defined in:** node_modules/@types/node/stream.d.ts:659 +**Returns:** `Promise`\<[`Workspace`](#workspace)[]\> -##### \[asyncIterator\]() +##### getFilepaths() ```ts -asyncIterator: AsyncIterator; +getFilepaths: (opts?) => Promise; ``` -**Returns:** `AsyncIterator`\<`any`, `any`, `any`\> +Get the affected filepaths based on the current inputs and state of the repository. Respects manual inputs provided by [\`builders.withFiles\`](namespaces/builders/#withfiles) if provided. + +This is a wrapped implementation of [\`builders.getFilepaths\`](namespaces/builders/#getfilepaths) that does not require the `graph` and `argv` arguments. -###### Inherited from +**Note:** that when used with `--affected`, there is a default limit of 100 files before this will switch to returning affected Workspace paths. Use `affectedThreshold: 0` to disable the limit. +**Parameters:** -`Duplex.[asyncIterator]` +| Parameter | Type | +| --------- | ------------------------------------------------------------- | +| `opts`? | [`FileGetterOptions`](namespaces/builders/#filegetteroptions) | -**Defined in:** node_modules/@types/node/stream.d.ts:654 +**Returns:** `Promise`\<`string`[]\> -##### \[captureRejectionSymbol\]()? +##### getWorkspaces() ```ts -optional [captureRejectionSymbol]( - error, - event, ... - args): void +getWorkspaces: (opts?) => Promise; ``` -###### Type Parameters +Get the affected Workspaces based on the current inputs and the state of the repository. +This function differs from `getAffected` in that it respects all input arguments provided by +[\`builders.withWorkspaces\`](namespaces/builders/#withworkspaces), [\`builders.withFiles\`](namespaces/builders/#withfiles) and [\`builders.withAffected\`](namespaces/builders/#withaffected). -| Type Parameter | -| -------------- | -| `K` | +This is a wrapped implementation of [\`builders.getWorkspaces\`](namespaces/builders/#getworkspaces) that does not require the `graph` and `argv` arguments. **Parameters:** -| Parameter | Type | -| --------- | -------------------- | -| `error` | `Error` | -| `event` | `string` \| `symbol` | -| ...`args` | `AnyRest` | - -**Returns:** `void` - -###### Inherited from - -`Duplex.[captureRejectionSymbol]` +| Parameter | Type | +| --------- | ----------------------------------------------------- | +| `opts`? | [`GetterOptions`](namespaces/builders/#getteroptions) | -**Defined in:** node_modules/@types/node/events.d.ts:136 +**Returns:** `Promise`\<[`Workspace`](#workspace)[]\> -##### addAbortListener() +##### graph ```ts -static addAbortListener(signal, resource): Disposable +graph: Graph; ``` -**Experimental** - -Listens once to the `abort` event on the provided `signal`. - -Listening to the `abort` event on abort signals is unsafe and may -lead to resource leaks since another third party with the signal can -call `e.stopImmediatePropagation()`. Unfortunately Node.js cannot change -this since it would violate the web standard. Additionally, the original -API makes it easy to forget to remove listeners. - -This API allows safely using `AbortSignal`s in Node.js APIs by solving these -two issues by listening to the event such that `stopImmediatePropagation` does -not prevent the listener from running. - -Returns a disposable so that it may be unsubscribed from more easily. +The full monorepo [\`Graph\`](#graph). -```js -import { addAbortListener } from 'node:events'; +##### logger -function example(signal) { - let disposable; - try { - signal.addEventListener('abort', (e) => e.stopImmediatePropagation()); - disposable = addAbortListener(signal, (e) => { - // Do something when signal is aborted. - }); - } finally { - disposable?.[Symbol.dispose](); - } -} +```ts +logger: Logger; ``` -**Parameters:** +Standard [\`Logger\`](#logger). This should _always_ be used in place of `console.log` methods unless you have +a specific need to write to standard out differently. -| Parameter | Type | -| ---------- | ------------------- | -| `signal` | `AbortSignal` | -| `resource` | (`event`) => `void` | +**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) -**Returns:** `Disposable` +--- -Disposable that removes the `abort` listener. +### PositionalArgv -###### Since +```ts +type PositionalArgv: { + _: (string | number)[]; + --: string[]; + $0: string; +}; +``` -v20.5.0 +Always present in Builder and Handler arguments as parsed by Yargs. -###### Inherited from +#### Type declaration -`Duplex.addAbortListener` +##### \_ -**Defined in:** node_modules/@types/node/events.d.ts:437 +```ts +_: (string | number)[]; +``` -##### addListener() +Positionals / non-option arguments. These will only be filled if you include `.positional()` or `.strictCommands(false)` in your `Builder`. -###### Call Signature +##### -- ```ts -addListener(event, listener): this +--: string[]; ``` -Event emitter -The defined events on documents including: - -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe - -**Parameters:** +Any content that comes after " -- " gets populated here. These are useful for spreading through to spawned `run` functions that may take extra options that you don't want to enumerate and validate. -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"close"` | -| `listener` | () => `void` | +##### $0 -**Returns:** `this` +```ts +$0: string; +``` -###### Inherited from +The script name or node command. Similar to `process.argv[1]` -`Duplex.addListener` +**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:1168 +## Config -###### Call Signature +### Config\ ```ts -addListener(event, listener): this +type Config: RootConfig | WorkspaceConfig; ``` -Event emitter -The defined events on documents including: - -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------------- | -| `event` | `"data"` | -| `listener` | (`chunk`) => `void` | - -**Returns:** `this` +Picks the correct config type between `RootConfig` and `WorkspaceConfig` based on whether the `root` property is set. Use this to help ensure your configs do not have any incorrect keys or values. -###### Inherited from +Satisfy a `RootConfig`: -`Duplex.addListener` +```ts +import type { Config } from 'onerepo'; -**Defined in:** node_modules/@types/node/stream.d.ts:1169 +export default { + root: true, +} satisfies Config; +``` -###### Call Signature +Satisfy a `WorkspaceConfig` with custom lifecycles on tasks: ```ts -addListener(event, listener): this +import type { Config } from 'onerepo'; + +export default { + tasks: { + stage: { + serial: ['$0 build'], + }, + }, +} satisfies Config<'stage'>; ``` -Event emitter -The defined events on documents including: +#### Type Parameters -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe +| Type Parameter | Default type | +| ----------------------------------------------- | ------------ | +| `CustomLifecycles` _extends_ `string` \| `void` | `void` | -**Parameters:** +**Defined in:** [modules/onerepo/src/types/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/index.ts) -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"drain"` | -| `listener` | () => `void` | +--- -**Returns:** `this` +### Lifecycle + +```ts +type Lifecycle: + | "pre-commit" + | "post-commit" + | "post-checkout" + | "pre-merge" + | "post-merge" + | "pre-push" + | "build" + | "pre-deploy" + | "pre-publish" + | "post-publish"; +``` -###### Inherited from +oneRepo comes with a pre-configured list of common lifecycles for grouping [tasks](/core/tasks/). -`Duplex.addListener` +**Defined in:** [modules/onerepo/src/types/tasks.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/tasks.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:1170 +--- -###### Call Signature +### RootConfig\ ```ts -addListener(event, listener): this +type RootConfig: { + changes: { + filenames: "hash" | "human"; + formatting: { + commit: string; + footer: string; + }; + prompts: "guided" | "semver"; + }; + codeowners: Record; + commands: { + directory: string | false; + ignore: RegExp; + }; + dependencies: { + dedupe: boolean; + mode: "strict" | "loose" | "off"; + }; + head: string; + ignore: string[]; + meta: Record; + plugins: Plugin[]; + root: true; + taskConfig: { + lifecycles: CustomLifecycles[]; + stashUnstaged: CustomLifecycles extends string ? Lifecycle | CustomLifecycles : Lifecycle[]; + }; + tasks: TaskConfig; + templateDir: string; + validation: { + schema: string | null; + }; + vcs: { + autoSyncHooks: boolean; + hooksPath: string; + provider: "github" | "gitlab" | "bitbucket" | "gitea"; + }; + visualizationUrl: string; +}; ``` -Event emitter -The defined events on documents including: - -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe +Setup configuration for the root of the repository. -**Parameters:** +#### Type Parameters -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"end"` | -| `listener` | () => `void` | +| Type Parameter | Default type | +| ----------------------------------------------- | ------------ | +| `CustomLifecycles` _extends_ `string` \| `void` | `void` | -**Returns:** `this` +#### Type declaration -###### Inherited from +##### changes? -`Duplex.addListener` +```ts +optional changes: { + filenames: "hash" | "human"; + formatting: { + commit: string; + footer: string; + }; + prompts: "guided" | "semver"; +}; +``` -**Defined in:** node_modules/@types/node/stream.d.ts:1171 +###### Type declaration -###### Call Signature +###### changes.filenames? ```ts -addListener(event, listener): this +optional filenames: "hash" | "human"; ``` -Event emitter -The defined events on documents including: +**Default:** `'hash'` -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe +To generate human-readable unique filenames for change files, ensure [human-id](https://www.npmjs.com/package/human-id) is installed. -**Parameters:** +###### changes.formatting? -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"error"` | -| `listener` | (`err`) => `void` | +```ts +optional formatting: { + commit: string; + footer: string; +}; +``` -**Returns:** `this` +###### Type declaration -###### Inherited from +**Default:** `{}` -`Duplex.addListener` +Override some formatting strings in generated changelog files. -**Defined in:** node_modules/@types/node/stream.d.ts:1172 +```ts title="onerepo.config.ts" +export default { + root: true, + changes: { + formatting: { + commit: '([${ref.short}](https://github.com/paularmstrong/onerepo/commit/${ref}))', + footer: + '> Full changelog [${fromRef.short}...${throughRef.short}](https://github.com/my-repo/commits/${fromRef}...${throughRef})', + }, + }, +}; +``` -###### Call Signature +###### changes.formatting.commit? ```ts -addListener(event, listener): this +optional commit: string; ``` -Event emitter -The defined events on documents including: - -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"finish"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from +**Default:** `'(${ref.short})'` -`Duplex.addListener` +Format how the commit ref will appear at the end of the first line of each change entry. -**Defined in:** node_modules/@types/node/stream.d.ts:1173 +Available replacement strings: +| Replacement | Description | +| --- | --- | +| `${ref.short}` | 8-character version of the commit ref | +| `${ref}` | Full commit ref | -###### Call Signature +###### changes.formatting.footer? ```ts -addListener(event, listener): this +optional footer: string; ``` -Event emitter -The defined events on documents including: +**Default:** `'_View git logs for full change list._'` -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe +Format the footer at the end of each version in the generated changelog files. -**Parameters:** +Available replacement strings: +| Replacement | Description | +| --- | --- | +| `${fromRef.short}` | 8-character version of the first commit ref in the version | +| `${fromRef}` | Full commit ref of the first commit in the version | +| `${through.short}` | 8-character version of the last commit ref in the version | +| `${through}` | Full commit ref of the last commit in the version | +| `${version}` | New version string | -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"pause"` | -| `listener` | () => `void` | +###### changes.prompts? -**Returns:** `this` +```ts +optional prompts: "guided" | "semver"; +``` -###### Inherited from +**Default:** `'guided'` -`Duplex.addListener` +Change the prompt question & answer style when adding change entries. -**Defined in:** node_modules/@types/node/stream.d.ts:1174 +- `'guided'`: Gives more detailed explanations when release types. +- `'semver'`: A simple choice list of semver release types. -###### Call Signature +##### codeowners? ```ts -addListener(event, listener): this +optional codeowners: Record; ``` -Event emitter -The defined events on documents including: +**Default:** `{}` -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe +Map of paths to array of owners. -**Parameters:** +When used with the [`codeowners` commands](https://onerepo.tools/core/codeowners/), this configuration enables syncing configurations from Workspaces to the appropriate root level CODEOWNERS file given your [`vcsProvider`](#vcsprovider) as well as verifying that the root file is up to date. -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"pipe"` | -| `listener` | (`src`) => `void` | +```ts title="onerepo.config.ts" +export default { + root: true, + codeowners: { + '*': ['@my-team', '@person'], + scripts: ['@infra-team'], + }, +}; +``` -**Returns:** `this` +##### commands? -###### Inherited from +```ts +optional commands: { + directory: string | false; + ignore: RegExp; +}; +``` -`Duplex.addListener` +Configuration for custom commands. -**Defined in:** node_modules/@types/node/stream.d.ts:1175 +###### Type declaration -###### Call Signature +###### commands.directory? ```ts -addListener(event, listener): this +optional directory: string | false; ``` -Event emitter -The defined events on documents including: - -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe +**Default:** `'commands'` -**Parameters:** +A string to use as filepaths to subcommands. We'll look for commands in all Workspaces using this string. If any are found, they'll be available from the CLI. -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"readable"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from +```ts title="onerepo.config.ts" +export default { + root: true, + commands: { + directory: 'commands', + }, +}; +``` -`Duplex.addListener` +Given the preceding configuration, commands will be searched for within the `commands/` directory at the root of the repository as well as a directory of the same name at the root of each Workspace: -**Defined in:** node_modules/@types/node/stream.d.ts:1176 +- `/commands/*` +- `//commands/*` -###### Call Signature +###### commands.ignore? ```ts -addListener(event, listener): this +optional ignore: RegExp; ``` -Event emitter -The defined events on documents including: - -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"resume"` | -| `listener` | () => `void` | - -**Returns:** `this` +**Default:** `/(/__\w+__/|\.test\.|\.spec\.|\.config\.)/` -###### Inherited from +Prevent reading matched files in the `commands.directory` as commands. -`Duplex.addListener` +When writing custom commands and Workspace-level subcommands, we may need to ignore certain files like tests, fixtures, and other helpers. Use a regular expression here to configure which files will be ignored when oneRepo parses and executes commands. -**Defined in:** node_modules/@types/node/stream.d.ts:1177 +```ts title="onerepo.config.ts" +export default { + root: true, + commands: { + ignore: /(/__\w+__/|\.test\.|\.spec\.|\.config\.)/, + }, +}; +``` -###### Call Signature +##### dependencies? ```ts -addListener(event, listener): this +optional dependencies: { + dedupe: boolean; + mode: "strict" | "loose" | "off"; +}; ``` -Event emitter -The defined events on documents including: - -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"unpipe"` | -| `listener` | (`src`) => `void` | +###### Type declaration -**Returns:** `this` +###### dependencies.dedupe? -###### Inherited from +```ts +optional dedupe: boolean; +``` -`Duplex.addListener` +**Default:** `true` -**Defined in:** node_modules/@types/node/stream.d.ts:1178 +When modifying dependencies using the `one dependencies` command, a `dedupe` will automatically be run to reduce duplicate package versions that overlap the requested ranges. Set this to `false` to disable this behavior. -###### Call Signature +###### dependencies.mode? ```ts -addListener(event, listener): this +optional mode: "strict" | "loose" | "off"; ``` -Event emitter -The defined events on documents including: +**Default:** `'loose'` -1. close -2. data -3. drain -4. end -5. error -6. finish -7. pause -8. pipe -9. readable -10. resume -11. unpipe +The dependency mode will be used for node module dependency management and verification. -**Parameters:** +- `off`: No validation will occur. Everything goes. +- `loose`: Reused third-party dependencies will be required to have semantic version overlap across unique branches of the Graph. +- `strict`: Versions of all dependencies across each discrete Workspace dependency tree must be strictly equal. -| Parameter | Type | -| ---------- | --------------------- | -| `event` | `string` \| `symbol` | -| `listener` | (...`args`) => `void` | +##### head? -**Returns:** `this` +```ts +optional head: string; +``` -###### Inherited from +**Default:** `'main'` -`Duplex.addListener` +The default branch of your repo? Probably `main`, but it might be something else, so it's helpful to put that here so that we can determine changed files accurately. -**Defined in:** node_modules/@types/node/stream.d.ts:1179 +```ts title="onerepo.config.ts" +export default { + root: true, + head: 'develop', +}; +``` -##### asIndexedPairs() +##### ignore? ```ts -asIndexedPairs(options?): Readable +optional ignore: string[]; ``` -This method returns a new stream with chunks of the underlying stream paired with a counter -in the form `[index, chunk]`. The first index value is `0` and it increases by 1 for each chunk produced. - -**Parameters:** +**Default:** `[]` -| Parameter | Type | -| ---------- | ------------------------------------ | -| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | +Array of fileglobs to ignore when calculating the changed Workspaces. -**Returns:** `Readable` +Periodically we may find that there are certain files or types of files that we _know_ for a fact do not affect the validity of the repository or any code. When this happens and the files are modified, unnecessary tasks and processes will be spun up that don't have any bearing on the outcome of the change. -a stream of indexed pairs. +To avoid extra processing, we can add file globs to ignore when calculated the afected Workspace graph. -###### Since +:::caution +This configuration should be used sparingly and with caution. It is better to do too much work as opposed to not enough. +::: -v17.5.0 +```ts title="onerepo.config.ts" +export default { + root: true, + ignore: ['.github/\*'], +}; +``` -###### Inherited from +##### meta? -`Duplex.asIndexedPairs` +```ts +optional meta: Record; +``` -**Defined in:** node_modules/@types/node/stream.d.ts:549 +**Default:** `{}` -##### compose() +A place to put any custom information or configuration. A helpful space for you to extend Workspace configurations for your own custom commands. -```ts -compose(stream, options?): T +```ts title="onerepo.config.ts" +export default { + root: true, + meta: { + tacos: 'are delicious', + }, +}; ``` -###### Type Parameters +##### plugins? -| Type Parameter | -| ------------------------------ | -| `T` _extends_ `ReadableStream` | +```ts +optional plugins: Plugin[]; +``` -**Parameters:** +**Default:** `[]` -| Parameter | Type | -| ----------------- | -------------------------------------------------------------------------------------------------- | -| `stream` | `T` \| `ComposeFnParam` \| `Iterable`\<`T`, `any`, `any`\> \| `AsyncIterable`\<`T`, `any`, `any`\> | -| `options`? | \{ `signal`: `AbortSignal`; \} | -| `options.signal`? | `AbortSignal` | +Add shared commands and extra handlers. See the [official plugin list](https://onerepo.tools/plugins/) for more information. -**Returns:** `T` +```ts title="onerepo.config.ts" +import { eslint } from '@onerepo/plugins-eslint'; +export default { + plugins: [eslint()], +}; +``` -###### Inherited from +##### root -`Duplex.compose` +```ts +root: true; +``` -**Defined in:** node_modules/@types/node/stream.d.ts:36 +Must be set to `true` in order to denote that this is the root of the repository. -##### cork() +##### taskConfig? ```ts -cork(): void +optional taskConfig: { + lifecycles: CustomLifecycles[]; + stashUnstaged: CustomLifecycles extends string ? Lifecycle | CustomLifecycles : Lifecycle[]; +}; ``` -The `writable.cork()` method forces all written data to be buffered in memory. -The buffered data will be flushed when either the [uncork](#uncork) or [end](#end) methods are called. - -The primary intent of `writable.cork()` is to accommodate a situation in which -several small chunks are written to the stream in rapid succession. Instead of -immediately forwarding them to the underlying destination, `writable.cork()` buffers all the chunks until `writable.uncork()` is called, which will pass them -all to `writable._writev()`, if present. This prevents a head-of-line blocking -situation where data is being buffered while waiting for the first small chunk -to be processed. However, use of `writable.cork()` without implementing `writable._writev()` may have an adverse effect on throughput. +Optional extra configuration for `tasks`. -See also: `writable.uncork()`, `writable._writev()`. +###### Type declaration -**Returns:** `void` +###### taskConfig.lifecycles? -###### Since +```ts +optional lifecycles: CustomLifecycles[]; +``` -v0.11.2 +**Default:** `[]` -###### Inherited from +Additional `task` lifecycles to make available. -`Duplex.cork` +See [\`Lifecycle\`](#lifecycle) for a list of pre-configured lifecycles. -**Defined in:** node_modules/@types/node/stream.d.ts:1127 +```ts title="onerepo.config.ts" +export default { + root: true, + tasks: { + lifecycles: ['deploy-staging'], + }, +}; +``` -##### debug() +###### taskConfig.stashUnstaged? ```ts -debug(contents): void +optional stashUnstaged: CustomLifecycles extends string ? Lifecycle | CustomLifecycles : Lifecycle[]; ``` -**Parameters:** - -| Parameter | Type | -| ---------- | --------- | -| `contents` | `unknown` | +**Default:** `['pre-commit']` +Stash unstaged changes before running these tasks and re-apply them after the task has completed. -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) +```ts title="onerepo.config.ts" +export default { + root: true, + tasks: { + stashUnstaged: ['pre-commit', 'post-checkout'], + }, +}; +``` -##### destroy() +##### tasks? ```ts -destroy(error?): this +optional tasks: TaskConfig; ``` -Destroy the stream. Optionally emit an `'error'` event, and emit a `'close'` event (unless `emitClose` is set to `false`). After this call, the readable -stream will release any internal resources and subsequent calls to `push()` will be ignored. - -Once `destroy()` has been called any further calls will be a no-op and no -further errors except from `_destroy()` may be emitted as `'error'`. - -Implementors should not override this method, but instead implement `readable._destroy()`. +**Default:** `{}` -**Parameters:** +Globally defined tasks per lifecycle. Tasks defined here will be assumed to run for all changes, regardless of affected Workspaces. Refer to the [`tasks` command](https://onerepo.tools/core/tasks/) specifications for details and examples. -| Parameter | Type | Description | -| --------- | ------- | -------------------------------------------------------- | -| `error`? | `Error` | Error which will be passed as payload in `'error'` event | +##### templateDir? -**Returns:** `this` +```ts +optional templateDir: string; +``` -###### Since +**Default:** `'./config/templates'` -v8.0.0 +Folder path for [`generate` command’s](https://onerepo.tools/core/generate/) templates. -###### Inherited from +##### validation? -`Duplex.destroy` +```ts +optional validation: { + schema: string | null; +}; +``` -**Defined in:** node_modules/@types/node/stream.d.ts:586 +###### Type declaration -##### drop() +###### validation.schema? ```ts -drop(limit, options?): Readable +optional schema: string | null; ``` -This method returns a new stream with the first _limit_ chunks dropped from the start. +**Default:** `undefined` -**Parameters:** +File path for custom Graph and configuration file validation schema. -| Parameter | Type | Description | -| ---------- | ------------------------------------ | ----------------------------------------------- | -| `limit` | `number` | the number of chunks to drop from the readable. | -| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | - | +##### vcs? -**Returns:** `Readable` +```ts +optional vcs: { + autoSyncHooks: boolean; + hooksPath: string; + provider: "github" | "gitlab" | "bitbucket" | "gitea"; +}; +``` -a stream with _limit_ chunks dropped from the start. +Version control system settings. -###### Since +###### Type declaration -v17.5.0 +###### vcs.autoSyncHooks? -###### Inherited from +```ts +optional autoSyncHooks: boolean; +``` -`Duplex.drop` +**Default:** `false` -**Defined in:** node_modules/@types/node/stream.d.ts:535 +Automatically set and sync oneRepo-managed git hooks. Change the directory for your git hooks with the [`vcs.hooksPath`](#vcshookspath) setting. Refer to the [Git hooks documentation](https://onerepo.tools/core/hooks/) to learn more. -##### emit() +```ts title="onerepo.config.ts" +export default { + root: true, + vcs: { + autoSyncHooks: false, + }, +}; +``` -###### Call Signature +###### vcs.hooksPath? ```ts -emit(event): boolean +optional hooksPath: string; ``` -**Parameters:** +**Default:** `'.hooks'` -| Parameter | Type | -| --------- | --------- | -| `event` | `"close"` | +Modify the default git hooks path for the repository. This will automatically be synchronized via `one hooks sync` unless explicitly disabled by setting [`vcs.autoSyncHooks`](#vcsautosynchooks) to `false`. -**Returns:** `boolean` +```ts title="onerepo.config.ts" +export default { + root: true, + vcs: { + hooksPath: '.githooks', + }, +}; +``` -###### Inherited from +###### vcs.provider? -`Duplex.emit` +```ts +optional provider: "github" | "gitlab" | "bitbucket" | "gitea"; +``` -**Defined in:** node_modules/@types/node/stream.d.ts:1180 +**Default:** `'github'` -###### Call Signature +The provider will be factored in to various commands, like `CODEOWNERS` generation. -```ts -emit(event, chunk): boolean +```ts title="onerepo.config.ts" +export default { + root: true, + vcs: { + provider: 'github', + }, +}; ``` -**Parameters:** +##### visualizationUrl? -| Parameter | Type | -| --------- | -------- | -| `event` | `"data"` | -| `chunk` | `any` | +```ts +optional visualizationUrl: string; +``` -**Returns:** `boolean` +**Default:** `'https://onerepo.tools/visualize/'` -###### Inherited from +Override the URL used to visualize the Graph. The Graph data will be attached the the `g` query parameter as a JSON string of the DAG, compressed using zLib deflate. -`Duplex.emit` +**Defined in:** [modules/onerepo/src/types/config-root.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/config-root.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:1181 +--- -###### Call Signature +### Task ```ts -emit(event): boolean +type Task: string | TaskDef | string[]; ``` -**Parameters:** - -| Parameter | Type | -| --------- | --------- | -| `event` | `"drain"` | - -**Returns:** `boolean` +A Task can either be a string or [\`TaskDef\`](#taskdef) object with extra options, or an array of strings. If provided as an array of strings, each command will be run sequentially, waiting for the previous to succeed. If one command fails, the rest in the sequence will not be run. -###### Inherited from +To run sequences of commands with `match` and `meta` information, you can pass an array of strings to the `cmd` property of a [\`TaskDef\`](#taskdef). -`Duplex.emit` +**Defined in:** [modules/onerepo/src/types/tasks.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/tasks.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:1182 +--- -###### Call Signature +### TaskConfig\ ```ts -emit(event): boolean +type TaskConfig: Partial>; ``` -**Parameters:** +#### Type Parameters -| Parameter | Type | -| --------- | ------- | -| `event` | `"end"` | +| Type Parameter | Default type | +| ----------------------------------------------- | ------------ | +| `CustomLifecycles` _extends_ `string` \| `void` | `void` | -**Returns:** `boolean` +**Defined in:** [modules/onerepo/src/types/tasks.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/tasks.ts) -###### Inherited from +--- -`Duplex.emit` +### TaskDef -**Defined in:** node_modules/@types/node/stream.d.ts:1183 +```ts +type TaskDef: { + cmd: string | string[]; + match: string | string[]; + meta: Record; +}; +``` -###### Call Signature +Tasks can optionally include meta information or only be run if the configured `match` glob string matches the modified files. If no files match, the individual task will not be run. ```ts -emit(event, err): boolean +export default { + tasks: { + 'pre-commit': { + parallel: [ + // Only run `astro check` if astro files have been modified + { match: '*.astro', cmd: '$0 astro check' }, + // Use a glob match with sequential tasks + { match: '*.{ts,js}', cmd: ['$0 lint', '$0 format'] }, + ], + }, + }, +} satisfies Config; ``` -**Parameters:** +#### Type declaration -| Parameter | Type | -| --------- | --------- | -| `event` | `"error"` | -| `err` | `Error` | +##### cmd -**Returns:** `boolean` +```ts +cmd: string | string[]; +``` -###### Inherited from +String command(s) to run. If provided as an array of strings, each command will be run sequentially, waiting for the previous to succeed. If one command fails, the rest in the sequence will not be run. -`Duplex.emit` +The commands can use replaced tokens: -**Defined in:** node_modules/@types/node/stream.d.ts:1184 +- `$0`: the oneRepo CLI for your repository +- `${workspaces}`: replaced with a space-separated list of Workspace names necessary for the given lifecycle -###### Call Signature +##### match? ```ts -emit(event): boolean +optional match: string | string[]; ``` -**Parameters:** +Glob file match. This will force the `cmd` to run if any of the paths in the modified files list match the glob. Conversely, if no files are matched, the `cmd` _will not_ run. -| Parameter | Type | -| --------- | ---------- | -| `event` | `"finish"` | +##### meta? -**Returns:** `boolean` +```ts +optional meta: Record; +``` -###### Inherited from +Extra information that will be provided only when listing tasks with the `--list` option from the `tasks` command. This object is helpful when creating a matrix of runners with GitHub actions or similar CI pipelines. -`Duplex.emit` +**Defined in:** [modules/onerepo/src/types/tasks.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/tasks.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:1185 +--- -###### Call Signature +### Tasks ```ts -emit(event): boolean +type Tasks: { + parallel: Task[]; + serial: Task[]; +}; ``` -**Parameters:** - -| Parameter | Type | -| --------- | --------- | -| `event` | `"pause"` | - -**Returns:** `boolean` +Individual [\`Task\`](#task)s in any [\`Lifecycle\`](#lifecycle) may be grouped to run either serial (one after the other) or in parallel (multiple at the same time). -###### Inherited from +#### Type declaration -`Duplex.emit` +##### parallel? -**Defined in:** node_modules/@types/node/stream.d.ts:1186 +```ts +optional parallel: Task[]; +``` -###### Call Signature +##### serial? ```ts -emit(event, src): boolean +optional serial: Task[]; ``` -**Parameters:** +**Defined in:** [modules/onerepo/src/types/tasks.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/tasks.ts) -| Parameter | Type | -| --------- | ---------- | -| `event` | `"pipe"` | -| `src` | `Readable` | +--- -**Returns:** `boolean` +### WorkspaceConfig\ + +```ts +type WorkspaceConfig: { + codeowners: Record; + commands: { + passthrough: Record; + }; + meta: Record; + tasks: TaskConfig; +}; +``` -###### Inherited from +#### Type Parameters -`Duplex.emit` +| Type Parameter | Default type | +| ----------------------------------------------- | ------------ | +| `CustomLifecycles` _extends_ `string` \| `void` | `void` | -**Defined in:** node_modules/@types/node/stream.d.ts:1187 +#### Type declaration -###### Call Signature +##### codeowners? ```ts -emit(event): boolean +optional codeowners: Record; ``` -**Parameters:** +**Default:** `{}`. +Map of paths to array of owners. -| Parameter | Type | -| --------- | ------------ | -| `event` | `"readable"` | +When used with the [`codeowners` commands](/core/codeowners/), this configuration enables syncing configurations from Workspaces to the appropriate root level CODEOWNERS file given your `RootConfig.vcs.provider` as well as verifying that the root file is up to date. -**Returns:** `boolean` +```ts title="onerepo.config.ts" +export default { + codeowners: { + '*': ['@my-team', '@person'], + scripts: ['@infra-team'], + }, +}; +``` + +##### commands? -###### Inherited from +```ts +optional commands: { + passthrough: Record; +}; +``` -`Duplex.emit` +Configuration for custom commands. To configure the commands directory, see [`RootConfig` `commands.directory`](#commandsdirectory). -**Defined in:** node_modules/@types/node/stream.d.ts:1188 +###### Type declaration -###### Call Signature +###### commands.passthrough ```ts -emit(event): boolean +passthrough: Record< + string, + { + command: string; + description: string; + } +>; ``` -**Parameters:** +**Default:** `{}` -| Parameter | Type | -| --------- | ---------- | -| `event` | `"resume"` | +Enable commands from installed dependencies. Similar to running `npx `, but pulled into the oneRepo CLI and able to be limited by Workspace. Passthrough commands _must_ have helpful descriptions. -**Returns:** `boolean` +```ts title="onerepo.config.ts" +export default { + commands: { + passthrough: { + astro: { description: 'Run Astro commands directly.' }, + start: { description: 'Run the Astro dev server.', command: 'astro dev --port=8000' }, + }, + }, +}; +``` + +##### meta? -###### Inherited from +```ts +optional meta: Record; +``` -`Duplex.emit` +**Default:** `{}` +A place to put any custom information or configuration. A helpful space for you to extend Workspace configurations for your own custom commands. -**Defined in:** node_modules/@types/node/stream.d.ts:1189 +```ts title="onerepo.config.ts" +export default { + meta: { + tacos: 'are delicious', + }, +}; +``` -###### Call Signature +##### tasks? ```ts -emit(event, src): boolean +optional tasks: TaskConfig; ``` -**Parameters:** +**Default:** `{}` +Tasks for this Workspace. These will be merged with global tasks and any other affected Workspace tasks. Refer to the [`tasks` command](/core/tasks/) specifications for details and examples. -| Parameter | Type | -| --------- | ---------- | -| `event` | `"unpipe"` | -| `src` | `Readable` | +:::tip[Merging tasks] +Each modified Workspace or Workspace that is affected by another Workspace's modifications will have its tasks evaluated and merged into the full set of tasks for each given lifecycle run. Check the [Tasks reference](/core/tasks/) to learn more. +::: -**Returns:** `boolean` +**Defined in:** [modules/onerepo/src/types/config-workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/config-workspace.ts) -###### Inherited from +## Graph -`Duplex.emit` +### getGraph() -**Defined in:** node_modules/@types/node/stream.d.ts:1190 +```ts +function getGraph(workingDir?): Graph; +``` -###### Call Signature +Get the [\`Graph\`](#graph) given a particular root working directory. If the working directory is not a monorepo's root, an empty `Graph` will be given in its place. ```ts -emit(event, ...args): boolean +const graph = getGraph(process.cwd()); +assert.ok(graph.isRoot); ``` **Parameters:** -| Parameter | Type | -| --------- | -------------------- | -| `event` | `string` \| `symbol` | -| ...`args` | `any`[] | - -**Returns:** `boolean` +| Parameter | Type | +| ------------- | -------- | +| `workingDir`? | `string` | -###### Inherited from +**Returns:** [`Graph`](#graph) +**Defined in:** [modules/graph/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/index.ts) -`Duplex.emit` +--- -**Defined in:** node_modules/@types/node/stream.d.ts:1191 +### Graph -##### end() +The oneRepo Graph is a representation of the entire repository’s [\`Workspaces\`](#workspace) and how they depend upon each other. Most commonly, you will want to use the Graph to get lists of Workspaces that either depend on some input or are dependencies thereof: ```ts -end(): this +const workspacesToCheck = graph.affected('tacos'); +for (const ws of workspacesToCheck) { + // verify no issues based on changes +} ``` -**Returns:** `this` - -###### Overrides +The `Graph` also includes various helpers for determining workspaces based on filepaths, name, and other factors. -`Duplex.end` +#### Accessors -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) +##### packageManager -##### error() +###### Get Signature ```ts -error(contents): void +get packageManager(): PackageManager ``` -**Parameters:** - -| Parameter | Type | -| ---------- | --------- | -| `contents` | `unknown` | - -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -##### eventNames() +Get the [PackageManager](#packagemanager-1) that this Graph depends on. This object allows you to run many common package management commands safely, agnostic of any particular flavor of package management. Works with npm, Yarn, and pnpm. ```ts -eventNames(): (string | symbol)[] +await graph.packageManager.install(); ``` -Returns an array listing the events for which the emitter has registered -listeners. The values in the array are strings or `Symbol`s. - -```js -import { EventEmitter } from 'node:events'; +**Returns:** [`PackageManager`](#packagemanager-1) +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -const myEE = new EventEmitter(); -myEE.on('foo', () => {}); -myEE.on('bar', () => {}); +##### root -const sym = Symbol('symbol'); -myEE.on(sym, () => {}); +###### Get Signature -console.log(myEE.eventNames()); -// Prints: [ 'foo', 'bar', Symbol(symbol) ] +```ts +get root(): Workspace ``` -**Returns:** (`string` \| `symbol`)[] - -###### Since - -v6.0.0 - -###### Inherited from - -`Duplex.eventNames` - -**Defined in:** node_modules/@types/node/events.d.ts:922 +This returns the [\`Workspace\`](#workspace) that is at the root of the repository. -##### every() +Regardless of how the `workspaces` are configured with the package manager, the root `package.json` is always registered as a Workspace. ```ts -every(fn, options?): Promise +const root = graph.root; +root.isRoot === true; ``` -This method is similar to `Array.prototype.every` and calls _fn_ on each chunk in the stream -to check if all awaited return values are truthy value for _fn_. Once an _fn_ call on a chunk -`await`ed return value is falsy, the stream is destroyed and the promise is fulfilled with `false`. -If all of the _fn_ calls on the chunks return a truthy value, the promise is fulfilled with `true`. - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | ----------------------------------------------------------- | ------------------------------------------------------------- | -| `fn` | (`data`, `options`?) => `boolean` \| `Promise`\<`boolean`\> | a function to call on each chunk of the stream. Async or not. | -| `options`? | `ArrayOptions` | - | +**Returns:** [`Workspace`](#workspace) +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -**Returns:** `Promise`\<`boolean`\> +##### workspaces -a promise evaluating to `true` if _fn_ returned a truthy value for every one of the chunks. +###### Get Signature -###### Since +```ts +get workspaces(): Workspace[] +``` -v17.5.0 +Get a list of all [\`Workspaces\`](#workspace) that are part of the repository {@Link Graph | `Graph`}. -###### Inherited from +```ts +for (const workspace of graph.workspaces) { + logger.info(workspace.name); +} +``` -`Duplex.every` +**Returns:** [`Workspace`](#workspace)[] +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:514 +#### Methods -##### filter() +##### affected() ```ts -filter(fn, options?): Readable +affected(source, type?): Workspace[] ``` -This method allows filtering the stream. For each chunk in the stream the _fn_ function will be called -and if it returns a truthy value, the chunk will be passed to the result stream. -If the _fn_ function returns a promise - that promise will be `await`ed. - -**Parameters:** +Get a list of [\`Workspaces\`](#workspace) that will be affected by the given source(s). This is equivalent to `graph.dependents(sources, true)`. See also [\`dependents\`](#dependents). -| Parameter | Type | Description | -| ---------- | ----------------------------------------------------------- | ---------------------------------------------------------- | -| `fn` | (`data`, `options`?) => `boolean` \| `Promise`\<`boolean`\> | a function to filter chunks from the stream. Async or not. | -| `options`? | `ArrayOptions` | - | +```ts +const dependents = graph.dependents(sources, true); +const affected = graph.affected(sources); -**Returns:** `Readable` +assert.isEqual(dependents, affecteed); +``` -a stream filtered with the predicate _fn_. +###### Type Parameters -###### Since +| Type Parameter | +| --------------------------------------------------- | +| `T` _extends_ `string` \| [`Workspace`](#workspace) | -v17.4.0, v16.14.0 +**Parameters:** -###### Inherited from +| Parameter | Type | Description | +| --------- | --------------------- | ------------------------------------------- | +| `source` | `T` \| `T`[] | - | +| `type`? | [`DepType`](#deptype) | Filter the dependents to a dependency type. | -`Duplex.filter` +**Returns:** [`Workspace`](#workspace)[] +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:442 +##### dependencies() -##### find() +```ts +dependencies( + sources?, + includeSelf?, + type?): Workspace[] +``` -###### Call Signature +Get all dependency [\`Workspaces\`](#workspace) of one or more input Workspaces or qualified names of Workspaces. This not only returns the direct dependencies, but all dependencies throughout the entire [\`Graph\`](#graph). This returns the opposite result of [\`dependents\`](#dependents). ```ts -find(fn, options?): Promise +for (const workspace of graph.dependencies('tacos')) { + logger.info(`"${workspace.name}" is a dependency of "tacos"`); +} ``` -This method is similar to `Array.prototype.find` and calls _fn_ on each chunk in the stream -to find a chunk with a truthy value for _fn_. Once an _fn_ call's awaited return value is truthy, -the stream is destroyed and the promise is fulfilled with value for which _fn_ returned a truthy value. -If all of the _fn_ calls on the chunks return a falsy value, the promise is fulfilled with `undefined`. - ###### Type Parameters -| Type Parameter | -| -------------- | -| `T` | +| Type Parameter | +| --------------------------------------------------- | +| `T` _extends_ `string` \| [`Workspace`](#workspace) | **Parameters:** -| Parameter | Type | Description | -| ---------- | ----------------------------------- | ------------------------------------------------------------- | -| `fn` | (`data`, `options`?) => `data is T` | a function to call on each chunk of the stream. Async or not. | -| `options`? | `ArrayOptions` | - | - -**Returns:** `Promise`\<`undefined` \| `T`\> - -a promise evaluating to the first chunk for which _fn_ evaluated with a truthy value, -or `undefined` if no element was found. - -###### Since - -v17.5.0 - -###### Inherited from - -`Duplex.find` +| Parameter | Type | Description | +| -------------- | --------------------- | ------------------------------------------------------------------------------------------------------ | +| `sources`? | `T` \| `T`[] | A list of [\`Workspaces\`](#workspace) by [\`name\`](#name)s or any available [\`aliases\`](#aliases). | +| `includeSelf`? | `boolean` | Whether to include the `Workspaces` for the input `sources` in the return array. | +| `type`? | [`DepType`](#deptype) | Filter the dependencies to a dependency type. | -**Defined in:** node_modules/@types/node/stream.d.ts:497 +**Returns:** [`Workspace`](#workspace)[] +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -###### Call Signature +##### dependents() ```ts -find(fn, options?): Promise +dependents( + sources?, + includeSelf?, + type?): Workspace[] ``` -This method is similar to `Array.prototype.find` and calls _fn_ on each chunk in the stream -to find a chunk with a truthy value for _fn_. Once an _fn_ call's awaited return value is truthy, -the stream is destroyed and the promise is fulfilled with value for which _fn_ returned a truthy value. -If all of the _fn_ calls on the chunks return a falsy value, the promise is fulfilled with `undefined`. - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | ----------------------------------------------------------- | ------------------------------------------------------------- | -| `fn` | (`data`, `options`?) => `boolean` \| `Promise`\<`boolean`\> | a function to call on each chunk of the stream. Async or not. | -| `options`? | `ArrayOptions` | - | - -**Returns:** `Promise`\<`any`\> +Get all dependent [\`Workspaces\`](#workspace) of one or more input Workspaces or qualified names of Workspaces. This not only returns the direct dependents, but all dependents throughout the entire [\`Graph\`](#graph). This returns the opposite result of [\`dependencies\`](#dependencies). -a promise evaluating to the first chunk for which _fn_ evaluated with a truthy value, -or `undefined` if no element was found. +```ts +for (const workspace of graph.dependents('tacos')) { + logger.info(`"${workspace.name}" depends on "tacos"`); +} +``` -###### Since +###### Type Parameters -v17.5.0 +| Type Parameter | +| --------------------------------------------------- | +| `T` _extends_ `string` \| [`Workspace`](#workspace) | -###### Inherited from +**Parameters:** -`Duplex.find` +| Parameter | Type | Description | +| -------------- | --------------------- | -------------------------------------------------------------------------------- | +| `sources`? | `T` \| `T`[] | One or more Workspaces by name or `Workspace` instance | +| `includeSelf`? | `boolean` | Whether to include the `Workspaces` for the input `sources` in the return array. | +| `type`? | [`DepType`](#deptype) | Filter the dependents to a dependency type. | -**Defined in:** node_modules/@types/node/stream.d.ts:501 +**Returns:** [`Workspace`](#workspace)[] +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -##### flatMap() +##### getAllByLocation() ```ts -flatMap(fn, options?): Readable +getAllByLocation(locations): Workspace[] ``` -This method returns a new stream by applying the given callback to each chunk of the stream -and then flattening the result. +Get all Workspaces given an array of filepaths. -It is possible to return a stream or another iterable or async iterable from _fn_ and the result streams -will be merged (flattened) into the returned stream. +```ts +const workspaces = graph.getAllByLocation([__dirname, 'file:///foo/bar']); +``` **Parameters:** -| Parameter | Type | Description | -| ---------- | ----------------------------- | --------------------------------------------------------------------------------------------- | -| `fn` | (`data`, `options`?) => `any` | a function to map over every chunk in the stream. May be async. May be a stream or generator. | -| `options`? | `ArrayOptions` | - | +| Parameter | Type | Description | +| ----------- | ---------- | ------------------------------------------------------------- | +| `locations` | `string`[] | A list of filepath strings. May be file URLs or string paths. | + +**Returns:** [`Workspace`](#workspace)[] +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -**Returns:** `Readable` +##### getAllByName() -a stream flat-mapped with the function _fn_. +```ts +getAllByName(names): Workspace[] +``` -###### Since +Get a list of [\`Workspaces\`](#workspace) by string names. -v17.5.0 +```ts +const workspaces = graph.getAllByName(['tacos', 'burritos']); +``` -###### Inherited from +**Parameters:** -`Duplex.flatMap` +| Parameter | Type | Description | +| --------- | ---------- | -------------------------------------------------------------------------------- | +| `names` | `string`[] | A list of Workspace [\`name\`](#name)s or any available [\`aliases\`](#aliases). | -**Defined in:** node_modules/@types/node/stream.d.ts:528 +**Returns:** [`Workspace`](#workspace)[] +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -##### forEach() +##### getByLocation() ```ts -forEach(fn, options?): Promise +getByLocation(location): Workspace ``` -This method allows iterating a stream. For each chunk in the stream the _fn_ function will be called. -If the _fn_ function returns a promise - that promise will be `await`ed. +Get the equivalent [\`Workspace\`](#workspace) for a filepath. This can be any location within a `Workspace`, not just its root. -This method is different from `for await...of` loops in that it can optionally process chunks concurrently. -In addition, a `forEach` iteration can only be stopped by having passed a `signal` option -and aborting the related AbortController while `for await...of` can be stopped with `break` or `return`. -In either case the stream will be destroyed. +```ts title="CommonJS compatible" +// in Node.js +graph.getByLocation(__dirname); +``` -This method is different from listening to the `'data'` event in that it uses the `readable` event -in the underlying machinary and can limit the number of concurrent _fn_ calls. +```ts title="ESM compatible" +graph.getByLocation(import.meta.url); +``` **Parameters:** -| Parameter | Type | Description | -| ---------- | ----------------------------------------------------- | ------------------------------------------------------------- | -| `fn` | (`data`, `options`?) => `void` \| `Promise`\<`void`\> | a function to call on each chunk of the stream. Async or not. | -| `options`? | `ArrayOptions` | - | - -**Returns:** `Promise`\<`void`\> +| Parameter | Type | Description | +| ---------- | -------- | ------------------------------- | +| `location` | `string` | A string or URL-based filepath. | -a promise for when the stream has finished. +**Returns:** [`Workspace`](#workspace) -###### Since +###### Throws -v17.5.0 +`Error` if no Workspace can be found. -###### Inherited from +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -`Duplex.forEach` +##### getByName() -**Defined in:** node_modules/@types/node/stream.d.ts:461 +```ts +getByName(name): Workspace +``` -##### from() +Get a [\`Workspace\`](#workspace) by string name. ```ts -static from(src): Duplex +const workspace = graph.getByName('my-cool-package'); ``` -A utility method for creating duplex streams. +**Parameters:** -- `Stream` converts writable stream into writable `Duplex` and readable stream - to `Duplex`. -- `Blob` converts into readable `Duplex`. -- `string` converts into readable `Duplex`. -- `ArrayBuffer` converts into readable `Duplex`. -- `AsyncIterable` converts into a readable `Duplex`. Cannot yield `null`. -- `AsyncGeneratorFunction` converts into a readable/writable transform - `Duplex`. Must take a source `AsyncIterable` as first parameter. Cannot yield - `null`. -- `AsyncFunction` converts into a writable `Duplex`. Must return - either `null` or `undefined` -- `Object ({ writable, readable })` converts `readable` and - `writable` into `Stream` and then combines them into `Duplex` where the - `Duplex` will write to the `writable` and read from the `readable`. -- `Promise` converts into readable `Duplex`. Value `null` is ignored. +| Parameter | Type | Description | +| --------- | -------- | ------------------------------------------------------------------------- | +| `name` | `string` | A Workspace’s [\`name\`](#name) or any available [\`aliases\`](#aliases). | -**Parameters:** +**Returns:** [`Workspace`](#workspace) -| Parameter | Type | -| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `src` | \| `string` \| `Object` \| `Promise`\<`any`\> \| `ArrayBuffer` \| `Stream` \| `Blob` \| `Iterable`\<`any`, `any`, `any`\> \| `AsyncIterable`\<`any`, `any`, `any`\> \| `AsyncGeneratorFunction` | +###### Throws -**Returns:** `Duplex` +`Error` if no Workspace exists with the given input `name`. -###### Since +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -v16.8.0 +--- -###### Inherited from +### Workspace -`Duplex.from` +#### Accessors -**Defined in:** node_modules/@types/node/stream.d.ts:1099 +##### aliases -##### fromWeb() +###### Get Signature ```ts -static fromWeb(duplexStream, options?): Duplex +get aliases(): string[] ``` -**Experimental** - -A utility method for creating a `Duplex` from a web `ReadableStream` and `WritableStream`. - -**Parameters:** - -| Parameter | Type | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `duplexStream` | \{ `readable`: `ReadableStream`\<`any`\>; `writable`: `WritableStream`\<`any`\>; \} | -| `duplexStream.readable` | `ReadableStream`\<`any`\> | -| `duplexStream.writable`? | `WritableStream`\<`any`\> | -| `options`? | `Pick`\<`DuplexOptions`, \| `"signal"` \| `"allowHalfOpen"` \| `"decodeStrings"` \| `"encoding"` \| `"highWaterMark"` \| `"objectMode"`\> | +Allow custom array of aliases. +If the fully qualified package name is scoped, this will include the un-scoped name -**Returns:** `Duplex` +**Returns:** `string`[] +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -###### Since +##### codeowners -v17.0.0 +###### Get Signature -###### Inherited from +```ts +get codeowners(): Required> +``` -`Duplex.fromWeb` +**Returns:** `Required`\<`Record`\<`string`, `string`[]\>\> +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:1143 +##### config -##### getEventListeners() +###### Get Signature ```ts -static getEventListeners(emitter, name): Function[] +get config(): Required ``` -Returns a copy of the array of listeners for the event named `eventName`. +Get the Workspace's configuration -For `EventEmitter`s this behaves exactly the same as calling `.listeners` on -the emitter. +**Returns:** `Required`\<[`RootConfig`](#rootconfigcustomlifecycles) \| [`WorkspaceConfig`](#workspaceconfigcustomlifecycles)\> +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -For `EventTarget`s this is the only way to get the event listeners for the -event target. This is useful for debugging and diagnostic purposes. +##### dependencies -```js -import { getEventListeners, EventEmitter } from 'node:events'; +###### Get Signature -{ - const ee = new EventEmitter(); - const listener = () => console.log('Events are fun'); - ee.on('foo', listener); - console.log(getEventListeners(ee, 'foo')); // [ [Function: listener] ] -} -{ - const et = new EventTarget(); - const listener = () => console.log('Events are fun'); - et.addEventListener('foo', listener); - console.log(getEventListeners(et, 'foo')); // [ [Function: listener] ] -} +```ts +get dependencies(): Record ``` -**Parameters:** - -| Parameter | Type | -| --------- | ---------------------------------------------------- | -| `emitter` | `EventEmitter`\<`DefaultEventMap`\> \| `EventTarget` | -| `name` | `string` \| `symbol` | - -**Returns:** `Function`[] - -###### Since +Get the `package.json` defined production dependencies for the Workspace. -v15.2.0, v14.17.0 +**Returns:** `Record`\<`string`, `string`\> -###### Inherited from +Map of modules to their version. -`Duplex.getEventListeners` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -**Defined in:** node_modules/@types/node/events.d.ts:358 +##### description -##### getMaxListeners() +###### Get Signature ```ts -static getMaxListeners(emitter): number +get description(): undefined | string ``` -Returns the currently set max amount of listeners. +Canonical to the `package.json` `"description"` field. -For `EventEmitter`s this behaves exactly the same as calling `.getMaxListeners` on -the emitter. +**Returns:** `undefined` \| `string` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -For `EventTarget`s this is the only way to get the max event listeners for the -event target. If the number of event handlers on a single EventTarget exceeds -the max set, the EventTarget will print a warning. +##### devDependencies -```js -import { getMaxListeners, setMaxListeners, EventEmitter } from 'node:events'; +###### Get Signature -{ - const ee = new EventEmitter(); - console.log(getMaxListeners(ee)); // 10 - setMaxListeners(11, ee); - console.log(getMaxListeners(ee)); // 11 -} -{ - const et = new EventTarget(); - console.log(getMaxListeners(et)); // 10 - setMaxListeners(11, et); - console.log(getMaxListeners(et)); // 11 -} +```ts +get devDependencies(): Record ``` -**Parameters:** +Get the `package.json` defined development dependencies for the Workspace. + +**Returns:** `Record`\<`string`, `string`\> + +Map of modules to their version. -| Parameter | Type | -| --------- | ---------------------------------------------------- | -| `emitter` | `EventEmitter`\<`DefaultEventMap`\> \| `EventTarget` | +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -**Returns:** `number` +##### isRoot -###### Since +###### Get Signature -v19.9.0 +```ts +get isRoot(): boolean +``` -###### Inherited from +Whether or not this Workspace is the root of the repository / Graph. -`Duplex.getMaxListeners` +**Returns:** `boolean` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -**Defined in:** node_modules/@types/node/events.d.ts:387 +##### location -##### getMaxListeners() +###### Get Signature ```ts -getMaxListeners(): number +get location(): string ``` -Returns the current max listener value for the `EventEmitter` which is either -set by `emitter.setMaxListeners(n)` or defaults to [defaultMaxListeners](#logstep). +Absolute path on the current filesystem to the Workspace. -**Returns:** `number` +**Returns:** `string` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -###### Since +##### main -v1.0.0 +###### Get Signature -###### Inherited from +```ts +get main(): string +``` -`Duplex.getMaxListeners` +**Returns:** `string` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -**Defined in:** node_modules/@types/node/events.d.ts:774 +##### name -##### info() +###### Get Signature ```ts -info(contents): void +get name(): string ``` -**Parameters:** +The full `name` of the Workspace, as defined in its `package.json` -| Parameter | Type | -| ---------- | --------- | -| `contents` | `unknown` | +**Returns:** `string` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) +##### packageJson -##### isDisturbed() +###### Get Signature ```ts -static isDisturbed(stream): boolean +get packageJson(): PackageJson ``` -Returns whether the stream has been read from or cancelled. +A full deep copy of the `package.json` file for the Workspace. Modifications to this object will not be preserved on the Workspace. -**Parameters:** +**Returns:** [`PackageJson`](#packagejson-1) +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -| Parameter | Type | -| --------- | ------------------------------ | -| `stream` | `ReadableStream` \| `Readable` | +##### peerDependencies -**Returns:** `boolean` +###### Get Signature + +```ts +get peerDependencies(): Record +``` -###### Since +Get the `package.json` defined peer dependencies for the Workspace. -v16.8.0 +**Returns:** `Record`\<`string`, `string`\> -###### Inherited from +Map of modules to their version. -`Duplex.isDisturbed` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:65 +##### private -##### isPaused() +###### Get Signature ```ts -isPaused(): boolean +get private(): boolean ``` -The `readable.isPaused()` method returns the current operating state of the `Readable`. -This is used primarily by the mechanism that underlies the `readable.pipe()` method. -In most typical cases, there will be no reason to use this method directly. +If a Workspace `package.json` is set to `private: true`, it will not be available to publish through NPM or other package management registries. -```js -const readable = new stream.Readable(); +**Returns:** `boolean` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) + +##### publishablePackageJson -readable.isPaused(); // === false -readable.pause(); -readable.isPaused(); // === true -readable.resume(); -readable.isPaused(); // === false +###### Get Signature + +```ts +get publishablePackageJson(): null | PublicPackageJson ``` -**Returns:** `boolean` +Get a version of the Workspace's `package.json` that is meant for publishing. -###### Since +This strips off `devDependencies` and applies appropriate [\`publishConfig\`](#publishconfig) values to the root of the `package.json`. This feature enables your monorepo to use source-dependencies and avoid manually building shared Workspaces for every change in order to see them take affect in dependent Workspaces. -v0.11.14 +To take advantage of this, configure your `package.json` root level to point to source files and the `publishConfig` entries to point to the build location of those entrypoints. -###### Inherited from +```json collapse={2-4} +{ + "name": "my-module", + "license": "MIT", + "type": "module", + "main": "./src/index.ts", + "publishConfig": { + "access": "public", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts" + } +} +``` -`Duplex.isPaused` +**Returns:** `null` \| [`PublicPackageJson`](#publicpackagejson) +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:295 +##### scope -##### iterator() +###### Get Signature ```ts -iterator(options?): AsyncIterator +get scope(): string ``` -The iterator created by this method gives users the option to cancel the destruction -of the stream if the `for await...of` loop is exited by `return`, `break`, or `throw`, -or if the iterator should destroy the stream if the stream emitted an error during iteration. +Get module name scope if there is one, eg `@onerepo` -**Parameters:** +**Returns:** `string` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) + +##### tasks -| Parameter | Type | Description | -| -------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `options`? | \{ `destroyOnReturn`: `boolean`; \} | - | -| `options.destroyOnReturn`? | `boolean` | When set to `false`, calling `return` on the async iterator, or exiting a `for await...of` iteration using a `break`, `return`, or `throw` will not destroy the stream. **Default: `true`**. | +###### Get Signature -**Returns:** `AsyncIterator`\<`any`, `any`, `any`\> +```ts +get tasks(): Partial> +``` -###### Since +Get the task configuration as defined in the `onerepo.config.js` file at the root of the Workspace. -v16.3.0 +**Returns:** `Partial`\<`Record`\<[`Lifecycle`](#lifecycle), [`Tasks`](#tasks)\>\> -###### Inherited from +If a config does not exist, an empty object will be given. -`Duplex.iterator` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:425 +##### version -##### ~~listenerCount()~~ +###### Get Signature ```ts -static listenerCount(emitter, eventName): number +get version(): undefined | string ``` -A class method that returns the number of listeners for the given `eventName` registered on the given `emitter`. +**Returns:** `undefined` \| `string` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) + +#### Methods -```js -import { EventEmitter, listenerCount } from 'node:events'; +##### getCodeowners() -const myEmitter = new EventEmitter(); -myEmitter.on('event', () => {}); -myEmitter.on('event', () => {}); -console.log(listenerCount(myEmitter, 'event')); -// Prints: 2 +```ts +getCodeowners(filepath): string[] ``` **Parameters:** -| Parameter | Type | Description | -| ----------- | ----------------------------------- | -------------------- | -| `emitter` | `EventEmitter`\<`DefaultEventMap`\> | The emitter to query | -| `eventName` | `string` \| `symbol` | The event name | - -**Returns:** `number` +| Parameter | Type | +| ---------- | -------- | +| `filepath` | `string` | -###### Since +**Returns:** `string`[] +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -v0.9.12 +##### getTasks() -###### Deprecated +```ts +getTasks(lifecycle): Required +``` -Since v3.2.0 - Use `listenerCount` instead. +Get a list of Workspace tasks for the given lifecycle -###### Inherited from +**Parameters:** -`Duplex.listenerCount` +| Parameter | Type | +| ----------- | -------- | +| `lifecycle` | `string` | -**Defined in:** node_modules/@types/node/events.d.ts:330 +**Returns:** `Required`\<[`Tasks`](#tasks)\> +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -##### listenerCount() +##### relative() ```ts -listenerCount(eventName, listener?): number +relative(to): string ``` -Returns the number of listeners listening for the event named `eventName`. -If `listener` is provided, it will return how many times the listener is found -in the list of the listeners of the event. - -###### Type Parameters +Get the relative path of an absolute path to the Workspace’s location root -| Type Parameter | -| -------------- | -| `K` | +```ts +const relativePath = workspace.relative('/some/absolute/path'); +``` **Parameters:** -| Parameter | Type | Description | -| ----------- | -------------------- | ---------------------------------------- | -| `eventName` | `string` \| `symbol` | The name of the event being listened for | -| `listener`? | `Function` | The event handler function | - -**Returns:** `number` - -###### Since - -v3.2.0 +| Parameter | Type | Description | +| --------- | -------- | ----------------- | +| `to` | `string` | Absolute filepath | -###### Inherited from +**Returns:** `string` -`Duplex.listenerCount` +Relative path to the workspace’s root location. -**Defined in:** node_modules/@types/node/events.d.ts:868 +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -##### listeners() +##### resolve() ```ts -listeners(eventName): Function[] +resolve(...pathSegments): string ``` -Returns a copy of the array of listeners for the event named `eventName`. +Resolve a full filepath within the Workspace given the path segments. Similar to Node.js's [path.resolve()](https://nodejs.org/dist/latest-v18.x/docs/api/path.html#pathresolvepaths). -```js -server.on('connection', (stream) => { - console.log('someone connected!'); -}); -console.log(util.inspect(server.listeners('connection'))); -// Prints: [ [Function] ] +```ts +const main = workspace.resolve(workspace.main); ``` -###### Type Parameters - -| Type Parameter | -| -------------- | -| `K` | - **Parameters:** -| Parameter | Type | -| ----------- | -------------------- | -| `eventName` | `string` \| `symbol` | - -**Returns:** `Function`[] - -###### Since +| Parameter | Type | Description | +| ----------------- | ---------- | ------------------------------------ | +| ...`pathSegments` | `string`[] | A sequence of paths or path segments | -v0.1.26 +**Returns:** `string` -###### Inherited from +Absolute path based on the input path segments -`Duplex.listeners` +**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) -**Defined in:** node_modules/@types/node/events.d.ts:787 +--- -##### log() +### DependencyType ```ts -log(contents): void +const DependencyType: { + DEV: 2; + PEER: 1; + PROD: 3; +}; ``` -**Parameters:** - -| Parameter | Type | -| ---------- | --------- | -| `contents` | `unknown` | - -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) +#### Type declaration -##### map() +##### DEV ```ts -map(fn, options?): Readable +readonly DEV: 2; ``` -This method allows mapping over the stream. The _fn_ function will be called for every chunk in the stream. -If the _fn_ function returns a promise - that promise will be `await`ed before being passed to the result stream. - -**Parameters:** +Development-only dependency (defined in `devDependencies` keys of `package.json`) -| Parameter | Type | Description | -| ---------- | ----------------------------- | --------------------------------------------------------------- | -| `fn` | (`data`, `options`?) => `any` | a function to map over every chunk in the stream. Async or not. | -| `options`? | `ArrayOptions` | - | +##### PEER -**Returns:** `Readable` +```ts +readonly PEER: 1; +``` -a stream mapped with the function _fn_. +Peer dependency (defined in `peerDependencies` key of `package.json`) -###### Since +##### PROD -v17.4.0, v16.14.0 +```ts +readonly PROD: 3; +``` -###### Inherited from +Production dependency (defined in `dependencies` of `package.json`) -`Duplex.map` +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -**Defined in:** node_modules/@types/node/stream.d.ts:433 +--- -##### off() +### DepType ```ts -off(eventName, listener): this +type DepType: 1 | 2 | 3; ``` -Alias for `emitter.removeListener()`. +Dependency type value. -###### Type Parameters +**See also:** +[\`DependencyType\`](#dependencytype) -| Type Parameter | -| -------------- | -| `K` | +**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) -**Parameters:** +--- -| Parameter | Type | -| ----------- | --------------------- | -| `eventName` | `string` \| `symbol` | -| `listener` | (...`args`) => `void` | +### GraphSchemaValidators -**Returns:** `this` +```ts +type GraphSchemaValidators: Record Schema & { + $required: boolean; +}>>; +``` -###### Since +Definition for `graph verify` JSON schema validators. -v10.0.0 +See [“Validating configurations”](/core/graph/#verifying-configurations) for more examples and use cases. -###### Inherited from +```ts +import type { GraphSchemaValidators } from 'onerepo'; -`Duplex.off` +export default { + '**': { + 'package.json': { + type: 'object', + $required: true, + properties: { + name: { type: 'string' }, + }, + required: ['name'], + }, + }, +} satisfies GraphSchemaValidators; +``` -**Defined in:** node_modules/@types/node/events.d.ts:747 +**Defined in:** [modules/onerepo/src/core/graph/schema.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/core/graph/schema.ts) -##### on() +## Logger -###### Call Signature +### bufferSubLogger() ```ts -static on( - emitter, - eventName, -options?): AsyncIterator +function bufferSubLogger(step): { + end: () => Promise; + logger: Logger; +}; ``` -```js -import { on, EventEmitter } from 'node:events'; -import process from 'node:process'; - -const ee = new EventEmitter(); +**Alpha** -// Emit later on -process.nextTick(() => { - ee.emit('foo', 'bar'); - ee.emit('foo', 42); -}); +Create a new Logger instance that has its output buffered up to a LogStep. -for await (const event of on(ee, 'foo')) { - // The execution of this inner block is synchronous and it - // processes one event at a time (even with await). Do not use - // if concurrent execution is required. - console.log(event); // prints ['bar'] [42] -} -// Unreachable here +```ts +const step = logger.createStep(name, { writePrefixes: false }); +const subLogger = bufferSubLogger(step); +const substep = subLogger.logger.createStep('Sub-step'); +substep.warning('This gets buffered'); +await substep.end(); +await subLogger.end(); +await step.en(); ``` -Returns an `AsyncIterator` that iterates `eventName` events. It will throw -if the `EventEmitter` emits `'error'`. It removes all listeners when -exiting the loop. The `value` returned by each iteration is an array -composed of the emitted event arguments. +**Parameters:** + +| Parameter | Type | +| --------- | --------------------- | +| `step` | [`LogStep`](#logstep) | -An `AbortSignal` can be used to cancel waiting on events: +**Returns:** ```ts +{ +end: () => Promise; +logger: Logger; +} -```js -import { on, EventEmitter } from 'node:events'; -import process from 'node:process'; +```` -const ac = new AbortController(); +##### end() -(async () => { - const ee = new EventEmitter(); +```ts +end: () => Promise; +```` - // Emit later on - process.nextTick(() => { - ee.emit('foo', 'bar'); - ee.emit('foo', 42); - }); +**Returns:** `Promise`\<`void`\> - for await (const event of on(ee, 'foo', { signal: ac.signal })) { - // The execution of this inner block is synchronous and it - // processes one event at a time (even with await). Do not use - // if concurrent execution is required. - console.log(event); // prints ['bar'] [42] - } - // Unreachable here -})(); +##### logger -process.nextTick(() => ac.abort()); +```ts +logger: Logger; ``` -Use the `close` option to specify an array of event names that will end the iteration: - -```js -import { on, EventEmitter } from 'node:events'; -import process from 'node:process'; +**Defined in:** [modules/logger/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/index.ts) -const ee = new EventEmitter(); +--- -// Emit later on -process.nextTick(() => { - ee.emit('foo', 'bar'); - ee.emit('foo', 42); - ee.emit('close'); -}); +### getLogger() -for await (const event of on(ee, 'foo', { close: ['close'] })) { - console.log(event); // prints ['bar'] [42] -} -// the loop will exit after 'close' is emitted -console.log('done'); // prints 'done' +```ts +function getLogger(opts?): Logger; ``` -**Parameters:** - -| Parameter | Type | -| ----------- | ----------------------------------- | -| `emitter` | `EventEmitter`\<`DefaultEventMap`\> | -| `eventName` | `string` \| `symbol` | -| `options`? | `StaticEventEmitterIteratorOptions` | - -**Returns:** `AsyncIterator`\<`any`[], `any`, `any`\> +This gets the logger singleton for use across all of oneRepo and its commands. -An `AsyncIterator` that iterates `eventName` events emitted by the `emitter` +Available directly as [\`HandlerExtra\`](#handlerextra) on [\`Handler\`](#handlercommandargv) functions: -###### Since +```ts +export const handler: Handler = (argv, { logger }) => { + logger.log('Hello!'); +}; +``` -v13.6.0, v12.16.0 +**Parameters:** -###### Inherited from +| Parameter | Type | +| --------- | ---------------------------------------------- | +| `opts`? | `Partial`\<[`LoggerOptions`](#loggeroptions)\> | -`Duplex.on` +**Returns:** [`Logger`](#logger) +**Defined in:** [modules/logger/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/index.ts) -**Defined in:** node_modules/@types/node/events.d.ts:303 +--- -###### Call Signature +### stepWrapper() ```ts -static on( - emitter, - eventName, -options?): AsyncIterator +function stepWrapper(options, fn): Promise; ``` -```js -import { on, EventEmitter } from 'node:events'; -import process from 'node:process'; - -const ee = new EventEmitter(); - -// Emit later on -process.nextTick(() => { - ee.emit('foo', 'bar'); - ee.emit('foo', 42); -}); +For cases where multiple processes need to be completed, but should be joined under a single [\`LogStep\`](#logstep) to avoid too much noisy output, this safely wraps an asynchronous function and handles step creation and completion, unless a `step` override is given. -for await (const event of on(ee, 'foo')) { - // The execution of this inner block is synchronous and it - // processes one event at a time (even with await). Do not use - // if concurrent execution is required. - console.log(event); // prints ['bar'] [42] +```ts +export async function exists(filename: string, { step }: Options = {}) { + return stepWrapper({ step, name: 'Step fallback name' }, (step) => { + return; // do some work + }); } -// Unreachable here ``` -Returns an `AsyncIterator` that iterates `eventName` events. It will throw -if the `EventEmitter` emits `'error'`. It removes all listeners when -exiting the loop. The `value` returned by each iteration is an array -composed of the emitted event arguments. +#### Type Parameters -An `AbortSignal` can be used to cancel waiting on events: - -```js -import { on, EventEmitter } from 'node:events'; -import process from 'node:process'; - -const ac = new AbortController(); - -(async () => { - const ee = new EventEmitter(); - - // Emit later on - process.nextTick(() => { - ee.emit('foo', 'bar'); - ee.emit('foo', 42); - }); - - for await (const event of on(ee, 'foo', { signal: ac.signal })) { - // The execution of this inner block is synchronous and it - // processes one event at a time (even with await). Do not use - // if concurrent execution is required. - console.log(event); // prints ['bar'] [42] - } - // Unreachable here -})(); - -process.nextTick(() => ac.abort()); -``` - -Use the `close` option to specify an array of event names that will end the iteration: - -```js -import { on, EventEmitter } from 'node:events'; -import process from 'node:process'; - -const ee = new EventEmitter(); - -// Emit later on -process.nextTick(() => { - ee.emit('foo', 'bar'); - ee.emit('foo', 42); - ee.emit('close'); -}); - -for await (const event of on(ee, 'foo', { close: ['close'] })) { - console.log(event); // prints ['bar'] [42] -} -// the loop will exit after 'close' is emitted -console.log('done'); // prints 'done' -``` - -**Parameters:** - -| Parameter | Type | -| ----------- | ----------------------------------- | -| `emitter` | `EventTarget` | -| `eventName` | `string` | -| `options`? | `StaticEventEmitterIteratorOptions` | - -**Returns:** `AsyncIterator`\<`any`[], `any`, `any`\> - -An `AsyncIterator` that iterates `eventName` events emitted by the `emitter` - -###### Since - -v13.6.0, v12.16.0 - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/events.d.ts:308 - -##### on() - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"close"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1192 - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------------- | -| `event` | `"data"` | -| `listener` | (`chunk`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1193 - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"drain"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1194 - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"end"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1195 - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"error"` | -| `listener` | (`err`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1196 - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"finish"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1197 - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"pause"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1198 - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"pipe"` | -| `listener` | (`src`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1199 - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"readable"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1200 - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"resume"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1201 - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"unpipe"` | -| `listener` | (`src`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1202 - -###### Call Signature - -```ts -on(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | --------------------- | -| `event` | `string` \| `symbol` | -| `listener` | (...`args`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.on` - -**Defined in:** node_modules/@types/node/stream.d.ts:1203 - -##### once() - -###### Call Signature - -```ts -static once( - emitter, - eventName, -options?): Promise -``` - -Creates a `Promise` that is fulfilled when the `EventEmitter` emits the given -event or that is rejected if the `EventEmitter` emits `'error'` while waiting. -The `Promise` will resolve with an array of all the arguments emitted to the -given event. - -This method is intentionally generic and works with the web platform [EventTarget](https://dom.spec.whatwg.org/#interface-eventtarget) interface, which has no special`'error'` event -semantics and does not listen to the `'error'` event. - -```js -import { once, EventEmitter } from 'node:events'; -import process from 'node:process'; - -const ee = new EventEmitter(); - -process.nextTick(() => { - ee.emit('myevent', 42); -}); - -const [value] = await once(ee, 'myevent'); -console.log(value); - -const err = new Error('kaboom'); -process.nextTick(() => { - ee.emit('error', err); -}); - -try { - await once(ee, 'myevent'); -} catch (err) { - console.error('error happened', err); -} -``` - -The special handling of the `'error'` event is only used when `events.once()` is used to wait for another event. If `events.once()` is used to wait for the -'`error'` event itself, then it is treated as any other kind of event without -special handling: - -```js -import { EventEmitter, once } from 'node:events'; - -const ee = new EventEmitter(); - -once(ee, 'error') - .then(([err]) => console.log('ok', err.message)) - .catch((err) => console.error('error', err.message)); - -ee.emit('error', new Error('boom')); - -// Prints: ok boom -``` - -An `AbortSignal` can be used to cancel waiting for the event: - -```js -import { EventEmitter, once } from 'node:events'; - -const ee = new EventEmitter(); -const ac = new AbortController(); - -async function foo(emitter, event, signal) { - try { - await once(emitter, event, { signal }); - console.log('event emitted!'); - } catch (error) { - if (error.name === 'AbortError') { - console.error('Waiting for the event was canceled!'); - } else { - console.error('There was an error', error.message); - } - } -} - -foo(ee, 'foo', ac.signal); -ac.abort(); // Abort waiting for the event -ee.emit('foo'); // Prints: Waiting for the event was canceled! -``` - -**Parameters:** - -| Parameter | Type | -| ----------- | ----------------------------------- | -| `emitter` | `EventEmitter`\<`DefaultEventMap`\> | -| `eventName` | `string` \| `symbol` | -| `options`? | `StaticEventEmitterOptions` | - -**Returns:** `Promise`\<`any`[]\> - -###### Since - -v11.13.0, v10.16.0 - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/events.d.ts:217 - -###### Call Signature - -```ts -static once( - emitter, - eventName, -options?): Promise -``` - -Creates a `Promise` that is fulfilled when the `EventEmitter` emits the given -event or that is rejected if the `EventEmitter` emits `'error'` while waiting. -The `Promise` will resolve with an array of all the arguments emitted to the -given event. - -This method is intentionally generic and works with the web platform [EventTarget](https://dom.spec.whatwg.org/#interface-eventtarget) interface, which has no special`'error'` event -semantics and does not listen to the `'error'` event. - -```js -import { once, EventEmitter } from 'node:events'; -import process from 'node:process'; - -const ee = new EventEmitter(); - -process.nextTick(() => { - ee.emit('myevent', 42); -}); - -const [value] = await once(ee, 'myevent'); -console.log(value); - -const err = new Error('kaboom'); -process.nextTick(() => { - ee.emit('error', err); -}); - -try { - await once(ee, 'myevent'); -} catch (err) { - console.error('error happened', err); -} -``` - -The special handling of the `'error'` event is only used when `events.once()` is used to wait for another event. If `events.once()` is used to wait for the -'`error'` event itself, then it is treated as any other kind of event without -special handling: - -```js -import { EventEmitter, once } from 'node:events'; - -const ee = new EventEmitter(); - -once(ee, 'error') - .then(([err]) => console.log('ok', err.message)) - .catch((err) => console.error('error', err.message)); - -ee.emit('error', new Error('boom')); - -// Prints: ok boom -``` - -An `AbortSignal` can be used to cancel waiting for the event: - -```js -import { EventEmitter, once } from 'node:events'; - -const ee = new EventEmitter(); -const ac = new AbortController(); - -async function foo(emitter, event, signal) { - try { - await once(emitter, event, { signal }); - console.log('event emitted!'); - } catch (error) { - if (error.name === 'AbortError') { - console.error('Waiting for the event was canceled!'); - } else { - console.error('There was an error', error.message); - } - } -} - -foo(ee, 'foo', ac.signal); -ac.abort(); // Abort waiting for the event -ee.emit('foo'); // Prints: Waiting for the event was canceled! -``` - -**Parameters:** - -| Parameter | Type | -| ----------- | --------------------------- | -| `emitter` | `EventTarget` | -| `eventName` | `string` | -| `options`? | `StaticEventEmitterOptions` | - -**Returns:** `Promise`\<`any`[]\> - -###### Since - -v11.13.0, v10.16.0 - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/events.d.ts:222 - -##### once() - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"close"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1204 - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------------- | -| `event` | `"data"` | -| `listener` | (`chunk`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1205 - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"drain"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1206 - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"end"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1207 - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"error"` | -| `listener` | (`err`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1208 - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"finish"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1209 - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"pause"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1210 - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"pipe"` | -| `listener` | (`src`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1211 - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"readable"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1212 - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"resume"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1213 - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"unpipe"` | -| `listener` | (`src`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1214 - -###### Call Signature - -```ts -once(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | --------------------- | -| `event` | `string` \| `symbol` | -| `listener` | (...`args`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.once` - -**Defined in:** node_modules/@types/node/stream.d.ts:1215 - -##### pause() - -```ts -pause(): this -``` - -The `readable.pause()` method will cause a stream in flowing mode to stop -emitting `'data'` events, switching out of flowing mode. Any data that -becomes available will remain in the internal buffer. - -```js -const readable = getReadableStreamSomehow(); -readable.on('data', (chunk) => { - console.log(`Received ${chunk.length} bytes of data.`); - readable.pause(); - console.log('There will be no additional data for 1 second.'); - setTimeout(() => { - console.log('Now data will start flowing again.'); - readable.resume(); - }, 1000); -}); -``` - -The `readable.pause()` method has no effect if there is a `'readable'` event listener. - -**Returns:** `this` - -###### Since - -v0.9.4 - -###### Inherited from - -`Duplex.pause` - -**Defined in:** node_modules/@types/node/stream.d.ts:259 - -##### pipe() - -```ts -pipe(destination, options?): T -``` - -###### Type Parameters - -| Type Parameter | -| ------------------------------ | -| `T` _extends_ `WritableStream` | - -**Parameters:** - -| Parameter | Type | -| -------------- | ----------------------- | -| `destination` | `T` | -| `options`? | \{ `end`: `boolean`; \} | -| `options.end`? | `boolean` | - -**Returns:** `T` - -###### Inherited from - -`Duplex.pipe` - -**Defined in:** node_modules/@types/node/stream.d.ts:30 - -##### prependListener() - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"close"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1216 - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------------- | -| `event` | `"data"` | -| `listener` | (`chunk`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1217 - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"drain"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1218 - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"end"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1219 - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"error"` | -| `listener` | (`err`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1220 - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"finish"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1221 - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"pause"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1222 - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"pipe"` | -| `listener` | (`src`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1223 - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"readable"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1224 - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"resume"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1225 - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"unpipe"` | -| `listener` | (`src`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1226 - -###### Call Signature - -```ts -prependListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | --------------------- | -| `event` | `string` \| `symbol` | -| `listener` | (...`args`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1227 - -##### prependOnceListener() - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"close"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1228 - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------------- | -| `event` | `"data"` | -| `listener` | (`chunk`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1229 - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"drain"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1230 - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"end"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1231 - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"error"` | -| `listener` | (`err`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1232 - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"finish"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1233 - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"pause"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1234 - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"pipe"` | -| `listener` | (`src`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1235 - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"readable"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1236 - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"resume"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1237 - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"unpipe"` | -| `listener` | (`src`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1238 - -###### Call Signature - -```ts -prependOnceListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | --------------------- | -| `event` | `string` \| `symbol` | -| `listener` | (...`args`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.prependOnceListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1239 - -##### push() - -```ts -push(chunk, encoding?): boolean -``` - -**Parameters:** - -| Parameter | Type | -| ----------- | ---------------- | -| `chunk` | `any` | -| `encoding`? | `BufferEncoding` | - -**Returns:** `boolean` - -###### Inherited from - -`Duplex.push` - -**Defined in:** node_modules/@types/node/stream.d.ts:415 - -##### rawListeners() - -```ts -rawListeners(eventName): Function[] -``` - -Returns a copy of the array of listeners for the event named `eventName`, -including any wrappers (such as those created by `.once()`). - -```js -import { EventEmitter } from 'node:events'; -const emitter = new EventEmitter(); -emitter.once('log', () => console.log('log once')); - -// Returns a new Array with a function `onceWrapper` which has a property -// `listener` which contains the original listener bound above -const listeners = emitter.rawListeners('log'); -const logFnWrapper = listeners[0]; - -// Logs "log once" to the console and does not unbind the `once` event -logFnWrapper.listener(); - -// Logs "log once" to the console and removes the listener -logFnWrapper(); - -emitter.on('log', () => console.log('log persistently')); -// Will return a new Array with a single function bound by `.on()` above -const newListeners = emitter.rawListeners('log'); - -// Logs "log persistently" twice -newListeners[0](); -emitter.emit('log'); -``` - -###### Type Parameters - -| Type Parameter | -| -------------- | -| `K` | - -**Parameters:** - -| Parameter | Type | -| ----------- | -------------------- | -| `eventName` | `string` \| `symbol` | - -**Returns:** `Function`[] - -###### Since - -v9.4.0 - -###### Inherited from - -`Duplex.rawListeners` - -**Defined in:** node_modules/@types/node/events.d.ts:818 - -##### read() - -```ts -read(size?): any -``` - -The `readable.read()` method reads data out of the internal buffer and -returns it. If no data is available to be read, `null` is returned. By default, -the data is returned as a `Buffer` object unless an encoding has been -specified using the `readable.setEncoding()` method or the stream is operating -in object mode. - -The optional `size` argument specifies a specific number of bytes to read. If -`size` bytes are not available to be read, `null` will be returned _unless_ the -stream has ended, in which case all of the data remaining in the internal buffer -will be returned. - -If the `size` argument is not specified, all of the data contained in the -internal buffer will be returned. - -The `size` argument must be less than or equal to 1 GiB. - -The `readable.read()` method should only be called on `Readable` streams -operating in paused mode. In flowing mode, `readable.read()` is called -automatically until the internal buffer is fully drained. - -```js -const readable = getReadableStreamSomehow(); - -// 'readable' may be triggered multiple times as data is buffered in -readable.on('readable', () => { - let chunk; - console.log('Stream is readable (new data received in buffer)'); - // Use a loop to make sure we read all currently available data - while (null !== (chunk = readable.read())) { - console.log(`Read ${chunk.length} bytes of data...`); - } -}); - -// 'end' will be triggered once when there is no more data available -readable.on('end', () => { - console.log('Reached end of stream.'); -}); -``` - -Each call to `readable.read()` returns a chunk of data, or `null`. The chunks -are not concatenated. A `while` loop is necessary to consume all data -currently in the buffer. When reading a large file `.read()` may return `null`, -having consumed all buffered content so far, but there is still more data to -come not yet buffered. In this case a new `'readable'` event will be emitted -when there is more data in the buffer. Finally the `'end'` event will be -emitted when there is no more data to come. - -Therefore to read a file's whole contents from a `readable`, it is necessary -to collect chunks across multiple `'readable'` events: - -```js -const chunks = []; - -readable.on('readable', () => { - let chunk; - while (null !== (chunk = readable.read())) { - chunks.push(chunk); - } -}); - -readable.on('end', () => { - const content = chunks.join(''); -}); -``` - -A `Readable` stream in object mode will always return a single item from -a call to `readable.read(size)`, regardless of the value of the `size` argument. - -If the `readable.read()` method returns a chunk of data, a `'data'` event will -also be emitted. - -Calling [read](#read) after the `'end'` event has -been emitted will return `null`. No runtime error will be raised. - -**Parameters:** - -| Parameter | Type | Description | -| --------- | -------- | --------------------------------------------------- | -| `size`? | `number` | Optional argument to specify how much data to read. | - -**Returns:** `any` - -###### Since - -v0.9.4 - -###### Inherited from - -`Duplex.read` - -**Defined in:** node_modules/@types/node/stream.d.ts:212 - -##### reduce() - -###### Call Signature - -```ts -reduce( - fn, - initial?, -options?): Promise -``` - -This method calls _fn_ on each chunk of the stream in order, passing it the result from the calculation -on the previous element. It returns a promise for the final value of the reduction. - -If no _initial_ value is supplied the first chunk of the stream is used as the initial value. -If the stream is empty, the promise is rejected with a `TypeError` with the `ERR_INVALID_ARGS` code property. - -The reducer function iterates the stream element-by-element which means that there is no _concurrency_ parameter -or parallelism. To perform a reduce concurrently, you can extract the async function to `readable.map` method. - -###### Type Parameters - -| Type Parameter | Default type | -| -------------- | ------------ | -| `T` | `any` | - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | --------------------------------------- | ------------------------------------------------------------------------ | -| `fn` | (`previous`, `data`, `options`?) => `T` | a reducer function to call over every chunk in the stream. Async or not. | -| `initial`? | `undefined` | the initial value to use in the reduction. | -| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | - | - -**Returns:** `Promise`\<`T`\> - -a promise for the final value of the reduction. - -###### Since - -v17.5.0 - -###### Inherited from - -`Duplex.reduce` - -**Defined in:** node_modules/@types/node/stream.d.ts:564 - -###### Call Signature - -```ts -reduce( - fn, - initial, -options?): Promise -``` - -This method calls _fn_ on each chunk of the stream in order, passing it the result from the calculation -on the previous element. It returns a promise for the final value of the reduction. - -If no _initial_ value is supplied the first chunk of the stream is used as the initial value. -If the stream is empty, the promise is rejected with a `TypeError` with the `ERR_INVALID_ARGS` code property. - -The reducer function iterates the stream element-by-element which means that there is no _concurrency_ parameter -or parallelism. To perform a reduce concurrently, you can extract the async function to `readable.map` method. - -###### Type Parameters - -| Type Parameter | Default type | -| -------------- | ------------ | -| `T` | `any` | - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | --------------------------------------- | ------------------------------------------------------------------------ | -| `fn` | (`previous`, `data`, `options`?) => `T` | a reducer function to call over every chunk in the stream. Async or not. | -| `initial` | `T` | the initial value to use in the reduction. | -| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | - | - -**Returns:** `Promise`\<`T`\> - -a promise for the final value of the reduction. - -###### Since - -v17.5.0 - -###### Inherited from - -`Duplex.reduce` - -**Defined in:** node_modules/@types/node/stream.d.ts:569 - -##### removeAllListeners() - -```ts -removeAllListeners(eventName?): this -``` - -Removes all listeners, or those of the specified `eventName`. - -It is bad practice to remove listeners added elsewhere in the code, -particularly when the `EventEmitter` instance was created by some other -component or module (e.g. sockets or file streams). - -Returns a reference to the `EventEmitter`, so that calls can be chained. - -**Parameters:** - -| Parameter | Type | -| ------------ | -------------------- | -| `eventName`? | `string` \| `symbol` | - -**Returns:** `this` - -###### Since - -v0.1.26 - -###### Inherited from - -`Duplex.removeAllListeners` - -**Defined in:** node_modules/@types/node/events.d.ts:758 - -##### removeListener() - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"close"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1240 - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------------- | -| `event` | `"data"` | -| `listener` | (`chunk`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1241 - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"drain"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1242 - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"end"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1243 - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"error"` | -| `listener` | (`err`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1244 - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"finish"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1245 - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"pause"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1246 - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"pipe"` | -| `listener` | (`src`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1247 - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"readable"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1248 - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------ | -| `event` | `"resume"` | -| `listener` | () => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1249 - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | ----------------- | -| `event` | `"unpipe"` | -| `listener` | (`src`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1250 - -###### Call Signature - -```ts -removeListener(event, listener): this -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | --------------------- | -| `event` | `string` \| `symbol` | -| `listener` | (...`args`) => `void` | - -**Returns:** `this` - -###### Inherited from - -`Duplex.removeListener` - -**Defined in:** node_modules/@types/node/stream.d.ts:1251 - -##### resume() - -```ts -resume(): this -``` - -The `readable.resume()` method causes an explicitly paused `Readable` stream to -resume emitting `'data'` events, switching the stream into flowing mode. - -The `readable.resume()` method can be used to fully consume the data from a -stream without actually processing any of that data: - -```js -getReadableStreamSomehow() - .resume() - .on('end', () => { - console.log('Reached the end, but did not read anything.'); - }); -``` - -The `readable.resume()` method has no effect if there is a `'readable'` event listener. - -**Returns:** `this` - -###### Since - -v0.9.4 - -###### Inherited from - -`Duplex.resume` - -**Defined in:** node_modules/@types/node/stream.d.ts:278 - -##### setDefaultEncoding() - -```ts -setDefaultEncoding(encoding): this -``` - -The `writable.setDefaultEncoding()` method sets the default `encoding` for a `Writable` stream. - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | ---------------- | ------------------------ | -| `encoding` | `BufferEncoding` | The new default encoding | - -**Returns:** `this` - -###### Since - -v0.11.15 - -###### Inherited from - -`Duplex.setDefaultEncoding` - -**Defined in:** node_modules/@types/node/stream.d.ts:1123 - -##### setEncoding() - -```ts -setEncoding(encoding): this -``` - -The `readable.setEncoding()` method sets the character encoding for -data read from the `Readable` stream. - -By default, no encoding is assigned and stream data will be returned as `Buffer` objects. Setting an encoding causes the stream data -to be returned as strings of the specified encoding rather than as `Buffer` objects. For instance, calling `readable.setEncoding('utf8')` will cause the -output data to be interpreted as UTF-8 data, and passed as strings. Calling `readable.setEncoding('hex')` will cause the data to be encoded in hexadecimal -string format. - -The `Readable` stream will properly handle multi-byte characters delivered -through the stream that would otherwise become improperly decoded if simply -pulled from the stream as `Buffer` objects. - -```js -const readable = getReadableStreamSomehow(); -readable.setEncoding('utf8'); -readable.on('data', (chunk) => { - assert.equal(typeof chunk, 'string'); - console.log('Got %d characters of string data:', chunk.length); -}); -``` - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | ---------------- | -------------------- | -| `encoding` | `BufferEncoding` | The encoding to use. | - -**Returns:** `this` - -###### Since - -v0.9.4 - -###### Inherited from - -`Duplex.setEncoding` - -**Defined in:** node_modules/@types/node/stream.d.ts:237 - -##### setMaxListeners() - -```ts -static setMaxListeners(n?, ...eventTargets?): void -``` - -```js -import { setMaxListeners, EventEmitter } from 'node:events'; - -const target = new EventTarget(); -const emitter = new EventEmitter(); - -setMaxListeners(5, target, emitter); -``` - -**Parameters:** - -| Parameter | Type | Description | -| ------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `n`? | `number` | A non-negative number. The maximum number of listeners per `EventTarget` event. | -| ...`eventTargets`? | (`EventEmitter`\<`DefaultEventMap`\> \| `EventTarget`)[] | Zero or more {EventTarget} or {EventEmitter} instances. If none are specified, `n` is set as the default max for all newly created {EventTarget} and {EventEmitter} objects. | - -**Returns:** `void` - -###### Since - -v15.4.0 - -###### Inherited from - -`Duplex.setMaxListeners` - -**Defined in:** node_modules/@types/node/events.d.ts:402 - -##### setMaxListeners() - -```ts -setMaxListeners(n): this -``` - -By default `EventEmitter`s will print a warning if more than `10` listeners are -added for a particular event. This is a useful default that helps finding -memory leaks. The `emitter.setMaxListeners()` method allows the limit to be -modified for this specific `EventEmitter` instance. The value can be set to `Infinity` (or `0`) to indicate an unlimited number of listeners. - -Returns a reference to the `EventEmitter`, so that calls can be chained. - -**Parameters:** - -| Parameter | Type | -| --------- | -------- | -| `n` | `number` | - -**Returns:** `this` - -###### Since - -v0.3.5 - -###### Inherited from - -`Duplex.setMaxListeners` - -**Defined in:** node_modules/@types/node/events.d.ts:768 - -##### some() - -```ts -some(fn, options?): Promise -``` - -This method is similar to `Array.prototype.some` and calls _fn_ on each chunk in the stream -until the awaited return value is `true` (or any truthy value). Once an _fn_ call on a chunk -`await`ed return value is truthy, the stream is destroyed and the promise is fulfilled with `true`. -If none of the _fn_ calls on the chunks return a truthy value, the promise is fulfilled with `false`. - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | ----------------------------------------------------------- | ------------------------------------------------------------- | -| `fn` | (`data`, `options`?) => `boolean` \| `Promise`\<`boolean`\> | a function to call on each chunk of the stream. Async or not. | -| `options`? | `ArrayOptions` | - | - -**Returns:** `Promise`\<`boolean`\> - -a promise evaluating to `true` if _fn_ returned a truthy value for at least one of the chunks. - -###### Since - -v17.5.0 - -###### Inherited from - -`Duplex.some` - -**Defined in:** node_modules/@types/node/stream.d.ts:483 - -##### take() - -```ts -take(limit, options?): Readable -``` - -This method returns a new stream with the first _limit_ chunks. - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | ------------------------------------ | ----------------------------------------------- | -| `limit` | `number` | the number of chunks to take from the readable. | -| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | - | - -**Returns:** `Readable` - -a stream with _limit_ chunks taken. - -###### Since - -v17.5.0 - -###### Inherited from - -`Duplex.take` - -**Defined in:** node_modules/@types/node/stream.d.ts:542 - -##### timing() - -```ts -timing(start, end): void -``` - -**Parameters:** - -| Parameter | Type | -| --------- | -------- | -| `start` | `string` | -| `end` | `string` | - -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -##### toArray() - -```ts -toArray(options?): Promise -``` - -This method allows easily obtaining the contents of a stream. - -As this method reads the entire stream into memory, it negates the benefits of streams. It's intended -for interoperability and convenience, not as the primary way to consume streams. - -**Parameters:** - -| Parameter | Type | -| ---------- | ------------------------------------ | -| `options`? | `Pick`\<`ArrayOptions`, `"signal"`\> | - -**Returns:** `Promise`\<`any`[]\> - -a promise containing an array with the contents of the stream. - -###### Since - -v17.5.0 - -###### Inherited from - -`Duplex.toArray` - -**Defined in:** node_modules/@types/node/stream.d.ts:473 - -##### toWeb() - -```ts -static toWeb(streamDuplex): { - readable: ReadableStream; - writable: WritableStream; -} -``` - -**Experimental** - -A utility method for creating a web `ReadableStream` and `WritableStream` from a `Duplex`. - -**Parameters:** - -| Parameter | Type | -| -------------- | -------- | -| `streamDuplex` | `Duplex` | - -**Returns:** ```ts -{ -readable: ReadableStream; -writable: WritableStream; -} - -```` - -###### readable - -```ts -readable: ReadableStream; -```` - -###### writable - -```ts -writable: WritableStream; -``` - -###### Since - -v17.0.0 - -###### Inherited from - -`Duplex.toWeb` - -**Defined in:** node_modules/@types/node/stream.d.ts:1134 - -##### uncork() - -```ts -uncork(): void -``` - -The `writable.uncork()` method flushes all data buffered since [cork](#cork) was called. - -When using `writable.cork()` and `writable.uncork()` to manage the buffering -of writes to a stream, defer calls to `writable.uncork()` using `process.nextTick()`. Doing so allows batching of all `writable.write()` calls that occur within a given Node.js event -loop phase. - -```js -stream.cork(); -stream.write('some '); -stream.write('data '); -process.nextTick(() => stream.uncork()); -``` - -If the `writable.cork()` method is called multiple times on a stream, the -same number of calls to `writable.uncork()` must be called to flush the buffered -data. - -```js -stream.cork(); -stream.write('some '); -stream.cork(); -stream.write('data '); -process.nextTick(() => { - stream.uncork(); - // The data will not be flushed until uncork() is called a second time. - stream.uncork(); -}); -``` - -See also: `writable.cork()`. - -**Returns:** `void` - -###### Since - -v0.11.2 - -###### Inherited from - -`Duplex.uncork` - -**Defined in:** node_modules/@types/node/stream.d.ts:1128 - -##### unpipe() - -```ts -unpipe(destination?): this -``` - -The `readable.unpipe()` method detaches a `Writable` stream previously attached -using the [pipe](#pipe) method. - -If the `destination` is not specified, then _all_ pipes are detached. - -If the `destination` is specified, but no pipe is set up for it, then -the method does nothing. - -```js -import fs from 'node:fs'; -const readable = getReadableStreamSomehow(); -const writable = fs.createWriteStream('file.txt'); -// All the data from readable goes into 'file.txt', -// but only for the first second. -readable.pipe(writable); -setTimeout(() => { - console.log('Stop writing to file.txt.'); - readable.unpipe(writable); - console.log('Manually close the file stream.'); - writable.end(); -}, 1000); -``` - -**Parameters:** - -| Parameter | Type | Description | -| -------------- | ---------------- | ---------------------------------- | -| `destination`? | `WritableStream` | Optional specific stream to unpipe | - -**Returns:** `this` - -###### Since - -v0.9.4 - -###### Inherited from - -`Duplex.unpipe` - -**Defined in:** node_modules/@types/node/stream.d.ts:322 - -##### unshift() - -```ts -unshift(chunk, encoding?): void -``` - -Passing `chunk` as `null` signals the end of the stream (EOF) and behaves the -same as `readable.push(null)`, after which no more data can be written. The EOF -signal is put at the end of the buffer and any buffered data will still be -flushed. - -The `readable.unshift()` method pushes a chunk of data back into the internal -buffer. This is useful in certain situations where a stream is being consumed by -code that needs to "un-consume" some amount of data that it has optimistically -pulled out of the source, so that the data can be passed on to some other party. - -The `stream.unshift(chunk)` method cannot be called after the `'end'` event -has been emitted or a runtime error will be thrown. - -Developers using `stream.unshift()` often should consider switching to -use of a `Transform` stream instead. See the `API for stream implementers` section for more information. - -```js -// Pull off a header delimited by \n\n. -// Use unshift() if we get too much. -// Call the callback with (error, header, stream). -import { StringDecoder } from 'node:string_decoder'; -function parseHeader(stream, callback) { - stream.on('error', callback); - stream.on('readable', onReadable); - const decoder = new StringDecoder('utf8'); - let header = ''; - function onReadable() { - let chunk; - while (null !== (chunk = stream.read())) { - const str = decoder.write(chunk); - if (str.includes('\n\n')) { - // Found the header boundary. - const split = str.split(/\n\n/); - header += split.shift(); - const remaining = split.join('\n\n'); - const buf = Buffer.from(remaining, 'utf8'); - stream.removeListener('error', callback); - // Remove the 'readable' listener before unshifting. - stream.removeListener('readable', onReadable); - if (buf.length) stream.unshift(buf); - // Now the body of the message can be read from the stream. - callback(null, header, stream); - return; - } - // Still reading the header. - header += str; - } - } -} -``` - -Unlike [push](#push), `stream.unshift(chunk)` will not -end the reading process by resetting the internal reading state of the stream. -This can cause unexpected results if `readable.unshift()` is called during a -read (i.e. from within a [\_read](#_read) implementation on a -custom stream). Following the call to `readable.unshift()` with an immediate [push](#push) will reset the reading state appropriately, -however it is best to simply avoid calling `readable.unshift()` while in the -process of performing a read. - -**Parameters:** - -| Parameter | Type | Description | -| ----------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `chunk` | `any` | Chunk of data to unshift onto the read queue. For streams not operating in object mode, `chunk` must be a {string}, {Buffer}, {TypedArray}, {DataView} or `null`. For object mode streams, `chunk` may be any JavaScript value. | -| `encoding`? | `BufferEncoding` | Encoding of string chunks. Must be a valid `Buffer` encoding, such as `'utf8'` or `'ascii'`. | - -**Returns:** `void` - -###### Since - -v0.9.11 - -###### Inherited from - -`Duplex.unshift` - -**Defined in:** node_modules/@types/node/stream.d.ts:388 - -##### warn() - -```ts -warn(contents): void -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | --------- | -| `contents` | `unknown` | - -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -##### wrap() - -```ts -wrap(stream): this -``` - -Prior to Node.js 0.10, streams did not implement the entire `node:stream` module API as it is currently defined. (See `Compatibility` for more -information.) - -When using an older Node.js library that emits `'data'` events and has a [pause](#pause) method that is advisory only, the `readable.wrap()` method can be used to create a `Readable` -stream that uses -the old stream as its data source. - -It will rarely be necessary to use `readable.wrap()` but the method has been -provided as a convenience for interacting with older Node.js applications and -libraries. - -```js -import { OldReader } from './old-api-module.js'; -import { Readable } from 'node:stream'; -const oreader = new OldReader(); -const myReader = new Readable().wrap(oreader); - -myReader.on('readable', () => { - myReader.read(); // etc. -}); -``` - -**Parameters:** - -| Parameter | Type | Description | -| --------- | ---------------- | ------------------------------ | -| `stream` | `ReadableStream` | An "old style" readable stream | - -**Returns:** `this` - -###### Since - -v0.9.4 - -###### Inherited from - -`Duplex.wrap` - -**Defined in:** node_modules/@types/node/stream.d.ts:414 - -##### write() - -###### Call Signature - -```ts -write( - chunk, - encoding?, - cb?): boolean -``` - -The `writable.write()` method writes some data to the stream, and calls the -supplied `callback` once the data has been fully handled. If an error -occurs, the `callback` will be called with the error as its -first argument. The `callback` is called asynchronously and before `'error'` is -emitted. - -The return value is `true` if the internal buffer is less than the `highWaterMark` configured when the stream was created after admitting `chunk`. -If `false` is returned, further attempts to write data to the stream should -stop until the `'drain'` event is emitted. - -While a stream is not draining, calls to `write()` will buffer `chunk`, and -return false. Once all currently buffered chunks are drained (accepted for -delivery by the operating system), the `'drain'` event will be emitted. -Once `write()` returns false, do not write more chunks -until the `'drain'` event is emitted. While calling `write()` on a stream that -is not draining is allowed, Node.js will buffer all written chunks until -maximum memory usage occurs, at which point it will abort unconditionally. -Even before it aborts, high memory usage will cause poor garbage collector -performance and high RSS (which is not typically released back to the system, -even after the memory is no longer required). Since TCP sockets may never -drain if the remote peer does not read the data, writing a socket that is -not draining may lead to a remotely exploitable vulnerability. - -Writing data while the stream is not draining is particularly -problematic for a `Transform`, because the `Transform` streams are paused -by default until they are piped or a `'data'` or `'readable'` event handler -is added. - -If the data to be written can be generated or fetched on demand, it is -recommended to encapsulate the logic into a `Readable` and use [pipe](#pipe). However, if calling `write()` is preferred, it is -possible to respect backpressure and avoid memory issues using the `'drain'` event: - -```js -function write(data, cb) { - if (!stream.write(data)) { - stream.once('drain', cb); - } else { - process.nextTick(cb); - } -} - -// Wait for cb to be called before doing any other write. -write('hello', () => { - console.log('Write completed, do more writes now.'); -}); -``` - -A `Writable` stream in object mode will always ignore the `encoding` argument. - -**Parameters:** - -| Parameter | Type | Description | -| ----------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `chunk` | `any` | Optional data to write. For streams not operating in object mode, `chunk` must be a {string}, {Buffer}, {TypedArray} or {DataView}. For object mode streams, `chunk` may be any JavaScript value other than `null`. | -| `encoding`? | `BufferEncoding` | The encoding, if `chunk` is a string. | -| `cb`? | (`error`) => `void` | - | - -**Returns:** `boolean` - -`false` if the stream wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`. - -###### Since - -v0.9.4 - -###### Inherited from - -`Duplex.write` - -**Defined in:** node_modules/@types/node/stream.d.ts:1121 - -###### Call Signature - -```ts -write(chunk, cb?): boolean -``` - -**Parameters:** - -| Parameter | Type | -| --------- | ------------------- | -| `chunk` | `any` | -| `cb`? | (`error`) => `void` | - -**Returns:** `boolean` - -###### Inherited from - -`Duplex.write` - -**Defined in:** node_modules/@types/node/stream.d.ts:1122 - -## Type Aliases - -### LineType - -```ts -type LineType: - | "start" - | "end" - | "error" - | "warn" - | "info" - | "log" - | "debug" - | "timing"; -``` - -**Defined in:** [modules/logger/src/types.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/types.ts) - ---- - -### LoggedBuffer - -```ts -type LoggedBuffer: { - contents: string; - group: string; - hasError: boolean; - type: LineType; - verbosity: Verbosity; -}; -``` - -#### Type declaration - -##### contents - -```ts -contents: string; -``` - -##### group? - -```ts -optional group: string; -``` - -##### hasError? - -```ts -optional hasError: boolean; -``` - -##### type - -```ts -type: LineType; -``` - -##### verbosity - -```ts -verbosity: Verbosity; -``` - -**Defined in:** [modules/logger/src/types.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/types.ts) - ---- - -### LogStepOptions - -```ts -type LogStepOptions: { - description: string; - name: string; - verbosity: Verbosity; -}; -``` - -#### Type declaration - -##### description? - -```ts -optional description: string; -``` - -##### name - -```ts -name: string; -``` - -##### verbosity - -```ts -verbosity: Verbosity; -``` - -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - -## Variables - -### defaultConfig - -```ts -const defaultConfig: Required; -``` - -**Defined in:** [modules/onerepo/src/setup/setup.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/setup/setup.ts) - -## Functions - -### restoreCursor() - -```ts -function restoreCursor(): void; -``` - -Gracefully restore the CLI cursor on exit. - -Prevent the cursor you have hidden interactively from remaining hidden if the process crashes. - -It does nothing if run in a non-TTY context. - -**Returns:** `void` - -#### Example - -``` -import restoreCursor from 'restore-cursor'; - -restoreCursor(); -``` - -**Defined in:** [modules/logger/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/index.ts) - -## Commands - -### Argv\ - -```ts -type Argv: Arguments; -``` - -Helper for combining local parsed arguments along with the default arguments provided by the oneRepo command module. - -#### Type Parameters - -| Type Parameter | Default type | Description | -| -------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `CommandArgv` | `object` | Arguments that will be parsed for this command, always a union with [\`DefaultArgv\`](#defaultargv) and [\`PositionalArgv\`](#positionalargv). | - -**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) - ---- - -### Builder()\ - -```ts -type Builder: (yargs) => Yargv; -``` - -Option argument parser for the given command. See [Yargs `.command(module)`](http://yargs.js.org/docs/#api-reference-commandmodule) for more, but note that only the object variant is not accepted – only function variants will be accepted in oneRepo commands. - -For common arguments that work in conjunction with [\`HandlerExtra\`](#handlerextra) methods like `getAffected()`, you can use helpers from the [\`builders\` namespace](namespaces/builders/), like [\`builders.withAffected()\`](namespaces/builders/#withaffected). - -```ts -type Argv = { - 'with-tacos'?: boolean; -}; - -export const builder: Builder = (yargs) => - yargs.usage(`$0 ${command}`).option('with-tacos', { - description: 'Include tacos', - type: 'boolean', - }); -``` - -#### Type Parameters - -| Type Parameter | Default type | Description | -| -------------- | ------------ | ---------------------------------------------- | -| `CommandArgv` | `object` | Arguments that will be parsed for this command | - -**Parameters:** - -| Parameter | Type | Description | -| --------- | ------- | --------------------------------------------------------------------------------------------------------- | -| `yargs` | `Yargs` | The Yargs instance. See [Yargs `.command(module)`](http://yargs.js.org/docs/#api-reference-commandmodule) | - -**Returns:** `Yargv`\<`CommandArgv`\> -**See also:** - -- [Yargs `.command(module)`](http://yargs.js.org/docs/#api-reference-commandmodule) for general usage. -- Common extensions via the [\`builders\`](namespaces/builders/) namespace. - -**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) - ---- - -### DefaultArgv - -```ts -type DefaultArgv: { - dry-run: boolean; - quiet: boolean; - skip-engine-check: boolean; - verbosity: number; -}; -``` - -Default arguments provided globally for all commands. These arguments are included by when using [\`Builder\`](#buildercommandargv) and [\`Handler\`](#handlercommandargv). - -#### Type declaration - -##### dry-run - -```ts -dry-run: boolean; -``` - -Whether the command should run non-destructive dry-mode. This prevents all subprocesses, files, and git operations from running unless explicitly specified as safe to run. - -Also internally sets `process.env.ONEREPO_DRY_RUN = 'true'`. - -**Default:** `false` - -##### quiet - -```ts -quiet: boolean; -``` - -Silence all logger output. Prevents _all_ stdout and stderr output from the logger entirely. - -**Default:** `false` - -##### skip-engine-check - -```ts -skip-engine-check: boolean; -``` - -Skip the engines check. When `false`, oneRepo will the current process's node version with the range for `engines.node` as defined in `package.json`. If not defined in the root `package.json`, this will be skipped. - -**Default:** `false` - -##### verbosity - -```ts -verbosity: number; -``` - -Verbosity level for the Logger. See Logger.verbosity for more information. - -**Default:** `3` -**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) - ---- - -### Handler()\ - -```ts -type Handler: (argv, extra) => Promise; -``` - -Command handler that includes oneRepo tools like `graph`, `logger`, and more. This function is type-safe if `Argv` is correctly passed through to the type definition. - -```ts -type Argv = { - 'with-tacos'?: boolean; -}; -export const handler: Handler = (argv, { logger }) => { - const { 'with-tacos': withTacos, '--': passthrough } = argv; - logger.log(withTacos ? 'Include tacos' : 'No tacos, thanks'); - logger.debug(passthrough); -}; -``` - -#### Type Parameters - -| Type Parameter | Default type | Description | -| -------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | -| `CommandArgv` | `object` | Arguments that will be parsed for this command. DefaultArguments will be automatically merged into this object for use within the handler. | - -**Parameters:** - -| Parameter | Type | -| --------- | ------------------------------------------- | -| `argv` | [`Argv`](#argvcommandargv)\<`CommandArgv`\> | -| `extra` | [`HandlerExtra`](#handlerextra) | - -**Returns:** `Promise`\<`void`\> -**See also:** - -- [Yargs `.command(module)`](http://yargs.js.org/docs/#api-reference-commandmodule) for general usage. -- [\`HandlerExtra\`](#handlerextra) for extended extra arguments provided above and beyond the scope of Yargs. - -**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) - ---- - -### HandlerExtra - -```ts -type HandlerExtra: { - config: Required; - getAffected: (opts?) => Promise; - getFilepaths: (opts?) => Promise; - getWorkspaces: (opts?) => Promise; - graph: Graph; - logger: Logger; -}; -``` - -Commands in oneRepo extend beyond what Yargs is able to provide by adding a second argument to the handler. - -All extras are available as the second argument on your [\`Handler\`](#handlercommandargv) - -```ts -export const handler: Handler = (argv, { getAffected, getFilepaths, getWorkspace, logger }) => { - logger.warn('Nothing to do!'); -}; -``` - -Overriding the affected threshold in `getFilepaths` - -```ts -export const handler: Handler = (argv, { getFilepaths }) => { - const filepaths = await getFilepaths({ affectedThreshold: 0 }); -}; -``` - -#### Type declaration - -##### config - -```ts -config: Required; -``` - -This repository’s oneRepo [config](#rootconfigcustomlifecycles), resolved with all defaults. - -##### getAffected() - -```ts -getAffected: (opts?) => Promise; -``` - -Get the affected Workspaces based on the current state of the repository. - -This is a wrapped implementation of [\`builders.getAffected\`](namespaces/builders/#getaffected) that does not require passing the `graph` argument. - -**Parameters:** - -| Parameter | Type | -| --------- | ----------------------------------------------------- | -| `opts`? | [`GetterOptions`](namespaces/builders/#getteroptions) | - -**Returns:** `Promise`\<[`Workspace`](#workspace)[]\> - -##### getFilepaths() - -```ts -getFilepaths: (opts?) => Promise; -``` - -Get the affected filepaths based on the current inputs and state of the repository. Respects manual inputs provided by [\`builders.withFiles\`](namespaces/builders/#withfiles) if provided. - -This is a wrapped implementation of [\`builders.getFilepaths\`](namespaces/builders/#getfilepaths) that does not require the `graph` and `argv` arguments. - -**Note:** that when used with `--affected`, there is a default limit of 100 files before this will switch to returning affected Workspace paths. Use `affectedThreshold: 0` to disable the limit. -**Parameters:** - -| Parameter | Type | -| --------- | ------------------------------------------------------------- | -| `opts`? | [`FileGetterOptions`](namespaces/builders/#filegetteroptions) | - -**Returns:** `Promise`\<`string`[]\> - -##### getWorkspaces() - -```ts -getWorkspaces: (opts?) => Promise; -``` - -Get the affected Workspaces based on the current inputs and the state of the repository. -This function differs from `getAffected` in that it respects all input arguments provided by -[\`builders.withWorkspaces\`](namespaces/builders/#withworkspaces), [\`builders.withFiles\`](namespaces/builders/#withfiles) and [\`builders.withAffected\`](namespaces/builders/#withaffected). - -This is a wrapped implementation of [\`builders.getWorkspaces\`](namespaces/builders/#getworkspaces) that does not require the `graph` and `argv` arguments. - -**Parameters:** - -| Parameter | Type | -| --------- | ----------------------------------------------------- | -| `opts`? | [`GetterOptions`](namespaces/builders/#getteroptions) | - -**Returns:** `Promise`\<[`Workspace`](#workspace)[]\> - -##### graph - -```ts -graph: Graph; -``` - -The full monorepo [\`Graph\`](#graph). - -##### logger - -```ts -logger: Logger; -``` - -Standard [\`Logger\`](#logger). This should _always_ be used in place of `console.log` methods unless you have -a specific need to write to standard out differently. - -**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) - ---- - -### PositionalArgv - -```ts -type PositionalArgv: { - _: (string | number)[]; - --: string[]; - $0: string; -}; -``` - -Always present in Builder and Handler arguments as parsed by Yargs. - -#### Type declaration - -##### \_ - -```ts -_: (string | number)[]; -``` - -Positionals / non-option arguments. These will only be filled if you include `.positional()` or `.strictCommands(false)` in your `Builder`. - -##### -- - -```ts ---: string[]; -``` - -Any content that comes after " -- " gets populated here. These are useful for spreading through to spawned `run` functions that may take extra options that you don't want to enumerate and validate. - -##### $0 - -```ts -$0: string; -``` - -The script name or node command. Similar to `process.argv[1]` - -**Defined in:** [modules/yargs/src/yargs.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/yargs/src/yargs.ts) - -## Config - -### Config\ - -```ts -type Config: RootConfig | WorkspaceConfig; -``` - -Picks the correct config type between `RootConfig` and `WorkspaceConfig` based on whether the `root` property is set. Use this to help ensure your configs do not have any incorrect keys or values. - -Satisfy a `RootConfig`: - -```ts -import type { Config } from 'onerepo'; - -export default { - root: true, -} satisfies Config; -``` - -Satisfy a `WorkspaceConfig` with custom lifecycles on tasks: - -```ts -import type { Config } from 'onerepo'; - -export default { - tasks: { - stage: { - serial: ['$0 build'], - }, - }, -} satisfies Config<'stage'>; -``` - -#### Type Parameters - -| Type Parameter | Default type | -| ----------------------------------------------- | ------------ | -| `CustomLifecycles` _extends_ `string` \| `void` | `void` | - -**Defined in:** [modules/onerepo/src/types/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/index.ts) - ---- - -### Lifecycle - -```ts -type Lifecycle: - | "pre-commit" - | "post-commit" - | "post-checkout" - | "pre-merge" - | "post-merge" - | "pre-push" - | "build" - | "pre-deploy" - | "pre-publish" - | "post-publish"; -``` - -oneRepo comes with a pre-configured list of common lifecycles for grouping [tasks](/core/tasks/). - -**Defined in:** [modules/onerepo/src/types/tasks.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/tasks.ts) - ---- - -### RootConfig\ - -```ts -type RootConfig: { - changes: { - filenames: "hash" | "human"; - formatting: { - commit: string; - footer: string; - }; - prompts: "guided" | "semver"; - }; - codeowners: Record; - commands: { - directory: string | false; - ignore: RegExp; - }; - dependencies: { - dedupe: boolean; - mode: "strict" | "loose" | "off"; - }; - head: string; - ignore: string[]; - meta: Record; - plugins: Plugin[]; - root: true; - taskConfig: { - lifecycles: CustomLifecycles[]; - stashUnstaged: CustomLifecycles extends string ? Lifecycle | CustomLifecycles : Lifecycle[]; - }; - tasks: TaskConfig; - templateDir: string; - validation: { - schema: string | null; - }; - vcs: { - autoSyncHooks: boolean; - hooksPath: string; - provider: "github" | "gitlab" | "bitbucket" | "gitea"; - }; - visualizationUrl: string; -}; -``` - -Setup configuration for the root of the repository. - -#### Type Parameters - -| Type Parameter | Default type | -| ----------------------------------------------- | ------------ | -| `CustomLifecycles` _extends_ `string` \| `void` | `void` | - -#### Type declaration - -##### changes? - -```ts -optional changes: { - filenames: "hash" | "human"; - formatting: { - commit: string; - footer: string; - }; - prompts: "guided" | "semver"; -}; -``` - -###### Type declaration - -###### changes.filenames? - -```ts -optional filenames: "hash" | "human"; -``` - -**Default:** `'hash'` - -To generate human-readable unique filenames for change files, ensure [human-id](https://www.npmjs.com/package/human-id) is installed. - -###### changes.formatting? - -```ts -optional formatting: { - commit: string; - footer: string; -}; -``` - -###### Type declaration - -**Default:** `{}` - -Override some formatting strings in generated changelog files. - -```ts title="onerepo.config.ts" -export default { - root: true, - changes: { - formatting: { - commit: '([${ref.short}](https://github.com/paularmstrong/onerepo/commit/${ref}))', - footer: - '> Full changelog [${fromRef.short}...${throughRef.short}](https://github.com/my-repo/commits/${fromRef}...${throughRef})', - }, - }, -}; -``` - -###### changes.formatting.commit? - -```ts -optional commit: string; -``` - -**Default:** `'(${ref.short})'` - -Format how the commit ref will appear at the end of the first line of each change entry. - -Available replacement strings: -| Replacement | Description | -| --- | --- | -| `${ref.short}` | 8-character version of the commit ref | -| `${ref}` | Full commit ref | - -###### changes.formatting.footer? - -```ts -optional footer: string; -``` - -**Default:** `'_View git logs for full change list._'` - -Format the footer at the end of each version in the generated changelog files. - -Available replacement strings: -| Replacement | Description | -| --- | --- | -| `${fromRef.short}` | 8-character version of the first commit ref in the version | -| `${fromRef}` | Full commit ref of the first commit in the version | -| `${through.short}` | 8-character version of the last commit ref in the version | -| `${through}` | Full commit ref of the last commit in the version | -| `${version}` | New version string | - -###### changes.prompts? - -```ts -optional prompts: "guided" | "semver"; -``` - -**Default:** `'guided'` - -Change the prompt question & answer style when adding change entries. - -- `'guided'`: Gives more detailed explanations when release types. -- `'semver'`: A simple choice list of semver release types. - -##### codeowners? - -```ts -optional codeowners: Record; -``` - -**Default:** `{}` - -Map of paths to array of owners. - -When used with the [`codeowners` commands](https://onerepo.tools/core/codeowners/), this configuration enables syncing configurations from Workspaces to the appropriate root level CODEOWNERS file given your [`vcsProvider`](#vcsprovider) as well as verifying that the root file is up to date. - -```ts title="onerepo.config.ts" -export default { - root: true, - codeowners: { - '*': ['@my-team', '@person'], - scripts: ['@infra-team'], - }, -}; -``` - -##### commands? - -```ts -optional commands: { - directory: string | false; - ignore: RegExp; -}; -``` - -Configuration for custom commands. - -###### Type declaration - -###### commands.directory? - -```ts -optional directory: string | false; -``` - -**Default:** `'commands'` - -A string to use as filepaths to subcommands. We'll look for commands in all Workspaces using this string. If any are found, they'll be available from the CLI. - -```ts title="onerepo.config.ts" -export default { - root: true, - commands: { - directory: 'commands', - }, -}; -``` - -Given the preceding configuration, commands will be searched for within the `commands/` directory at the root of the repository as well as a directory of the same name at the root of each Workspace: - -- `/commands/*` -- `//commands/*` - -###### commands.ignore? - -```ts -optional ignore: RegExp; -``` - -**Default:** `/(/__\w+__/|\.test\.|\.spec\.|\.config\.)/` - -Prevent reading matched files in the `commands.directory` as commands. - -When writing custom commands and Workspace-level subcommands, we may need to ignore certain files like tests, fixtures, and other helpers. Use a regular expression here to configure which files will be ignored when oneRepo parses and executes commands. - -```ts title="onerepo.config.ts" -export default { - root: true, - commands: { - ignore: /(/__\w+__/|\.test\.|\.spec\.|\.config\.)/, - }, -}; -``` - -##### dependencies? - -```ts -optional dependencies: { - dedupe: boolean; - mode: "strict" | "loose" | "off"; -}; -``` - -###### Type declaration - -###### dependencies.dedupe? - -```ts -optional dedupe: boolean; -``` - -**Default:** `true` - -When modifying dependencies using the `one dependencies` command, a `dedupe` will automatically be run to reduce duplicate package versions that overlap the requested ranges. Set this to `false` to disable this behavior. - -###### dependencies.mode? - -```ts -optional mode: "strict" | "loose" | "off"; -``` - -**Default:** `'loose'` - -The dependency mode will be used for node module dependency management and verification. - -- `off`: No validation will occur. Everything goes. -- `loose`: Reused third-party dependencies will be required to have semantic version overlap across unique branches of the Graph. -- `strict`: Versions of all dependencies across each discrete Workspace dependency tree must be strictly equal. - -##### head? - -```ts -optional head: string; -``` - -**Default:** `'main'` - -The default branch of your repo? Probably `main`, but it might be something else, so it's helpful to put that here so that we can determine changed files accurately. - -```ts title="onerepo.config.ts" -export default { - root: true, - head: 'develop', -}; -``` - -##### ignore? - -```ts -optional ignore: string[]; -``` - -**Default:** `[]` - -Array of fileglobs to ignore when calculating the changed Workspaces. - -Periodically we may find that there are certain files or types of files that we _know_ for a fact do not affect the validity of the repository or any code. When this happens and the files are modified, unnecessary tasks and processes will be spun up that don't have any bearing on the outcome of the change. - -To avoid extra processing, we can add file globs to ignore when calculated the afected Workspace graph. - -:::caution -This configuration should be used sparingly and with caution. It is better to do too much work as opposed to not enough. -::: - -```ts title="onerepo.config.ts" -export default { - root: true, - ignore: ['.github/\*'], -}; -``` - -##### meta? - -```ts -optional meta: Record; -``` - -**Default:** `{}` - -A place to put any custom information or configuration. A helpful space for you to extend Workspace configurations for your own custom commands. - -```ts title="onerepo.config.ts" -export default { - root: true, - meta: { - tacos: 'are delicious', - }, -}; -``` - -##### plugins? - -```ts -optional plugins: Plugin[]; -``` - -**Default:** `[]` - -Add shared commands and extra handlers. See the [official plugin list](https://onerepo.tools/plugins/) for more information. - -```ts title="onerepo.config.ts" -import { eslint } from '@onerepo/plugins-eslint'; -export default { - plugins: [eslint()], -}; -``` - -##### root - -```ts -root: true; -``` - -Must be set to `true` in order to denote that this is the root of the repository. - -##### taskConfig? - -```ts -optional taskConfig: { - lifecycles: CustomLifecycles[]; - stashUnstaged: CustomLifecycles extends string ? Lifecycle | CustomLifecycles : Lifecycle[]; -}; -``` - -Optional extra configuration for `tasks`. - -###### Type declaration - -###### taskConfig.lifecycles? - -```ts -optional lifecycles: CustomLifecycles[]; -``` - -**Default:** `[]` - -Additional `task` lifecycles to make available. - -See [\`Lifecycle\`](#lifecycle) for a list of pre-configured lifecycles. - -```ts title="onerepo.config.ts" -export default { - root: true, - tasks: { - lifecycles: ['deploy-staging'], - }, -}; -``` - -###### taskConfig.stashUnstaged? - -```ts -optional stashUnstaged: CustomLifecycles extends string ? Lifecycle | CustomLifecycles : Lifecycle[]; -``` - -**Default:** `['pre-commit']` -Stash unstaged changes before running these tasks and re-apply them after the task has completed. - -```ts title="onerepo.config.ts" -export default { - root: true, - tasks: { - stashUnstaged: ['pre-commit', 'post-checkout'], - }, -}; -``` - -##### tasks? - -```ts -optional tasks: TaskConfig; -``` - -**Default:** `{}` - -Globally defined tasks per lifecycle. Tasks defined here will be assumed to run for all changes, regardless of affected Workspaces. Refer to the [`tasks` command](https://onerepo.tools/core/tasks/) specifications for details and examples. - -##### templateDir? - -```ts -optional templateDir: string; -``` - -**Default:** `'./config/templates'` - -Folder path for [`generate` command’s](https://onerepo.tools/core/generate/) templates. - -##### validation? - -```ts -optional validation: { - schema: string | null; -}; -``` - -###### Type declaration - -###### validation.schema? - -```ts -optional schema: string | null; -``` - -**Default:** `undefined` - -File path for custom Graph and configuration file validation schema. - -##### vcs? - -```ts -optional vcs: { - autoSyncHooks: boolean; - hooksPath: string; - provider: "github" | "gitlab" | "bitbucket" | "gitea"; -}; -``` - -Version control system settings. - -###### Type declaration - -###### vcs.autoSyncHooks? - -```ts -optional autoSyncHooks: boolean; -``` - -**Default:** `false` - -Automatically set and sync oneRepo-managed git hooks. Change the directory for your git hooks with the [`vcs.hooksPath`](#vcshookspath) setting. Refer to the [Git hooks documentation](https://onerepo.tools/core/hooks/) to learn more. - -```ts title="onerepo.config.ts" -export default { - root: true, - vcs: { - autoSyncHooks: false, - }, -}; -``` - -###### vcs.hooksPath? - -```ts -optional hooksPath: string; -``` - -**Default:** `'.hooks'` - -Modify the default git hooks path for the repository. This will automatically be synchronized via `one hooks sync` unless explicitly disabled by setting [`vcs.autoSyncHooks`](#vcsautosynchooks) to `false`. - -```ts title="onerepo.config.ts" -export default { - root: true, - vcs: { - hooksPath: '.githooks', - }, -}; -``` - -###### vcs.provider? - -```ts -optional provider: "github" | "gitlab" | "bitbucket" | "gitea"; -``` - -**Default:** `'github'` - -The provider will be factored in to various commands, like `CODEOWNERS` generation. - -```ts title="onerepo.config.ts" -export default { - root: true, - vcs: { - provider: 'github', - }, -}; -``` - -##### visualizationUrl? - -```ts -optional visualizationUrl: string; -``` - -**Default:** `'https://onerepo.tools/visualize/'` - -Override the URL used to visualize the Graph. The Graph data will be attached the the `g` query parameter as a JSON string of the DAG, compressed using zLib deflate. - -**Defined in:** [modules/onerepo/src/types/config-root.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/config-root.ts) - ---- - -### Task - -```ts -type Task: string | TaskDef | string[]; -``` - -A Task can either be a string or [\`TaskDef\`](#taskdef) object with extra options, or an array of strings. If provided as an array of strings, each command will be run sequentially, waiting for the previous to succeed. If one command fails, the rest in the sequence will not be run. - -To run sequences of commands with `match` and `meta` information, you can pass an array of strings to the `cmd` property of a [\`TaskDef\`](#taskdef). - -**Defined in:** [modules/onerepo/src/types/tasks.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/tasks.ts) - ---- - -### TaskConfig\ - -```ts -type TaskConfig: Partial>; -``` - -#### Type Parameters - -| Type Parameter | Default type | -| ----------------------------------------------- | ------------ | -| `CustomLifecycles` _extends_ `string` \| `void` | `void` | - -**Defined in:** [modules/onerepo/src/types/tasks.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/tasks.ts) - ---- - -### TaskDef - -```ts -type TaskDef: { - cmd: string | string[]; - match: string | string[]; - meta: Record; -}; -``` - -Tasks can optionally include meta information or only be run if the configured `match` glob string matches the modified files. If no files match, the individual task will not be run. - -```ts -export default { - tasks: { - 'pre-commit': { - parallel: [ - // Only run `astro check` if astro files have been modified - { match: '*.astro', cmd: '$0 astro check' }, - // Use a glob match with sequential tasks - { match: '*.{ts,js}', cmd: ['$0 lint', '$0 format'] }, - ], - }, - }, -} satisfies Config; -``` - -#### Type declaration - -##### cmd - -```ts -cmd: string | string[]; -``` - -String command(s) to run. If provided as an array of strings, each command will be run sequentially, waiting for the previous to succeed. If one command fails, the rest in the sequence will not be run. - -The commands can use replaced tokens: - -- `$0`: the oneRepo CLI for your repository -- `${workspaces}`: replaced with a space-separated list of Workspace names necessary for the given lifecycle - -##### match? - -```ts -optional match: string | string[]; -``` - -Glob file match. This will force the `cmd` to run if any of the paths in the modified files list match the glob. Conversely, if no files are matched, the `cmd` _will not_ run. - -##### meta? - -```ts -optional meta: Record; -``` - -Extra information that will be provided only when listing tasks with the `--list` option from the `tasks` command. This object is helpful when creating a matrix of runners with GitHub actions or similar CI pipelines. - -**Defined in:** [modules/onerepo/src/types/tasks.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/tasks.ts) - ---- - -### Tasks - -```ts -type Tasks: { - parallel: Task[]; - serial: Task[]; -}; -``` - -Individual [\`Task\`](#task)s in any [\`Lifecycle\`](#lifecycle) may be grouped to run either serial (one after the other) or in parallel (multiple at the same time). - -#### Type declaration - -##### parallel? - -```ts -optional parallel: Task[]; -``` - -##### serial? - -```ts -optional serial: Task[]; -``` - -**Defined in:** [modules/onerepo/src/types/tasks.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/tasks.ts) - ---- - -### WorkspaceConfig\ - -```ts -type WorkspaceConfig: { - codeowners: Record; - commands: { - passthrough: Record; - }; - meta: Record; - tasks: TaskConfig; -}; -``` - -#### Type Parameters - -| Type Parameter | Default type | -| ----------------------------------------------- | ------------ | -| `CustomLifecycles` _extends_ `string` \| `void` | `void` | - -#### Type declaration - -##### codeowners? - -```ts -optional codeowners: Record; -``` - -**Default:** `{}`. -Map of paths to array of owners. - -When used with the [`codeowners` commands](/core/codeowners/), this configuration enables syncing configurations from Workspaces to the appropriate root level CODEOWNERS file given your `RootConfig.vcs.provider` as well as verifying that the root file is up to date. - -```ts title="onerepo.config.ts" -export default { - codeowners: { - '*': ['@my-team', '@person'], - scripts: ['@infra-team'], - }, -}; -``` - -##### commands? - -```ts -optional commands: { - passthrough: Record; -}; -``` - -Configuration for custom commands. To configure the commands directory, see [`RootConfig` `commands.directory`](#commandsdirectory). - -###### Type declaration - -###### commands.passthrough - -```ts -passthrough: Record< - string, - { - command: string; - description: string; - } ->; -``` - -**Default:** `{}` - -Enable commands from installed dependencies. Similar to running `npx `, but pulled into the oneRepo CLI and able to be limited by Workspace. Passthrough commands _must_ have helpful descriptions. - -```ts title="onerepo.config.ts" -export default { - commands: { - passthrough: { - astro: { description: 'Run Astro commands directly.' }, - start: { description: 'Run the Astro dev server.', command: 'astro dev --port=8000' }, - }, - }, -}; -``` - -##### meta? - -```ts -optional meta: Record; -``` - -**Default:** `{}` -A place to put any custom information or configuration. A helpful space for you to extend Workspace configurations for your own custom commands. - -```ts title="onerepo.config.ts" -export default { - meta: { - tacos: 'are delicious', - }, -}; -``` - -##### tasks? - -```ts -optional tasks: TaskConfig; -``` - -**Default:** `{}` -Tasks for this Workspace. These will be merged with global tasks and any other affected Workspace tasks. Refer to the [`tasks` command](/core/tasks/) specifications for details and examples. - -:::tip[Merging tasks] -Each modified Workspace or Workspace that is affected by another Workspace's modifications will have its tasks evaluated and merged into the full set of tasks for each given lifecycle run. Check the [Tasks reference](/core/tasks/) to learn more. -::: - -**Defined in:** [modules/onerepo/src/types/config-workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/types/config-workspace.ts) - -## Graph - -### getGraph() - -```ts -function getGraph(workingDir?): Graph; -``` - -Get the [\`Graph\`](#graph) given a particular root working directory. If the working directory is not a monorepo's root, an empty `Graph` will be given in its place. - -```ts -const graph = getGraph(process.cwd()); -assert.ok(graph.isRoot); -``` - -**Parameters:** - -| Parameter | Type | -| ------------- | -------- | -| `workingDir`? | `string` | - -**Returns:** [`Graph`](#graph) -**Defined in:** [modules/graph/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/index.ts) - ---- - -### Graph - -The oneRepo Graph is a representation of the entire repository’s [\`Workspaces\`](#workspace) and how they depend upon each other. Most commonly, you will want to use the Graph to get lists of Workspaces that either depend on some input or are dependencies thereof: - -```ts -const workspacesToCheck = graph.affected('tacos'); -for (const ws of workspacesToCheck) { - // verify no issues based on changes -} -``` - -The `Graph` also includes various helpers for determining workspaces based on filepaths, name, and other factors. - -#### Accessors - -##### packageManager - -###### Get Signature - -```ts -get packageManager(): PackageManager -``` - -Get the [PackageManager](#packagemanager-1) that this Graph depends on. This object allows you to run many common package management commands safely, agnostic of any particular flavor of package management. Works with npm, Yarn, and pnpm. - -```ts -await graph.packageManager.install(); -``` - -**Returns:** [`PackageManager`](#packagemanager-1) -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) - -##### root - -###### Get Signature - -```ts -get root(): Workspace -``` - -This returns the [\`Workspace\`](#workspace) that is at the root of the repository. - -Regardless of how the `workspaces` are configured with the package manager, the root `package.json` is always registered as a Workspace. - -```ts -const root = graph.root; -root.isRoot === true; -``` - -**Returns:** [`Workspace`](#workspace) -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) - -##### workspaces - -###### Get Signature - -```ts -get workspaces(): Workspace[] -``` - -Get a list of all [\`Workspaces\`](#workspace) that are part of the repository {@Link Graph | `Graph`}. - -```ts -for (const workspace of graph.workspaces) { - logger.info(workspace.name); -} -``` - -**Returns:** [`Workspace`](#workspace)[] -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) - -#### Methods - -##### affected() - -```ts -affected(source, type?): Workspace[] -``` - -Get a list of [\`Workspaces\`](#workspace) that will be affected by the given source(s). This is equivalent to `graph.dependents(sources, true)`. See also [\`dependents\`](#dependents). - -```ts -const dependents = graph.dependents(sources, true); -const affected = graph.affected(sources); - -assert.isEqual(dependents, affecteed); -``` - -###### Type Parameters - -| Type Parameter | -| --------------------------------------------------- | -| `T` _extends_ `string` \| [`Workspace`](#workspace) | - -**Parameters:** - -| Parameter | Type | Description | -| --------- | --------------------- | ------------------------------------------- | -| `source` | `T` \| `T`[] | - | -| `type`? | [`DepType`](#deptype) | Filter the dependents to a dependency type. | - -**Returns:** [`Workspace`](#workspace)[] -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) - -##### dependencies() - -```ts -dependencies( - sources?, - includeSelf?, - type?): Workspace[] -``` - -Get all dependency [\`Workspaces\`](#workspace) of one or more input Workspaces or qualified names of Workspaces. This not only returns the direct dependencies, but all dependencies throughout the entire [\`Graph\`](#graph). This returns the opposite result of [\`dependents\`](#dependents). - -```ts -for (const workspace of graph.dependencies('tacos')) { - logger.info(`"${workspace.name}" is a dependency of "tacos"`); -} -``` - -###### Type Parameters - -| Type Parameter | -| --------------------------------------------------- | -| `T` _extends_ `string` \| [`Workspace`](#workspace) | - -**Parameters:** - -| Parameter | Type | Description | -| -------------- | --------------------- | ------------------------------------------------------------------------------------------------------ | -| `sources`? | `T` \| `T`[] | A list of [\`Workspaces\`](#workspace) by [\`name\`](#name)s or any available [\`aliases\`](#aliases). | -| `includeSelf`? | `boolean` | Whether to include the `Workspaces` for the input `sources` in the return array. | -| `type`? | [`DepType`](#deptype) | Filter the dependencies to a dependency type. | - -**Returns:** [`Workspace`](#workspace)[] -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) - -##### dependents() - -```ts -dependents( - sources?, - includeSelf?, - type?): Workspace[] -``` - -Get all dependent [\`Workspaces\`](#workspace) of one or more input Workspaces or qualified names of Workspaces. This not only returns the direct dependents, but all dependents throughout the entire [\`Graph\`](#graph). This returns the opposite result of [\`dependencies\`](#dependencies). - -```ts -for (const workspace of graph.dependents('tacos')) { - logger.info(`"${workspace.name}" depends on "tacos"`); -} -``` - -###### Type Parameters - -| Type Parameter | -| --------------------------------------------------- | -| `T` _extends_ `string` \| [`Workspace`](#workspace) | - -**Parameters:** - -| Parameter | Type | Description | -| -------------- | --------------------- | -------------------------------------------------------------------------------- | -| `sources`? | `T` \| `T`[] | One or more Workspaces by name or `Workspace` instance | -| `includeSelf`? | `boolean` | Whether to include the `Workspaces` for the input `sources` in the return array. | -| `type`? | [`DepType`](#deptype) | Filter the dependents to a dependency type. | - -**Returns:** [`Workspace`](#workspace)[] -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) - -##### getAllByLocation() - -```ts -getAllByLocation(locations): Workspace[] -``` - -Get all Workspaces given an array of filepaths. - -```ts -const workspaces = graph.getAllByLocation([__dirname, 'file:///foo/bar']); -``` - -**Parameters:** - -| Parameter | Type | Description | -| ----------- | ---------- | ------------------------------------------------------------- | -| `locations` | `string`[] | A list of filepath strings. May be file URLs or string paths. | - -**Returns:** [`Workspace`](#workspace)[] -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) - -##### getAllByName() - -```ts -getAllByName(names): Workspace[] -``` - -Get a list of [\`Workspaces\`](#workspace) by string names. - -```ts -const workspaces = graph.getAllByName(['tacos', 'burritos']); -``` - -**Parameters:** - -| Parameter | Type | Description | -| --------- | ---------- | -------------------------------------------------------------------------------- | -| `names` | `string`[] | A list of Workspace [\`name\`](#name)s or any available [\`aliases\`](#aliases). | - -**Returns:** [`Workspace`](#workspace)[] -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) - -##### getByLocation() - -```ts -getByLocation(location): Workspace -``` - -Get the equivalent [\`Workspace\`](#workspace) for a filepath. This can be any location within a `Workspace`, not just its root. - -```ts title="CommonJS compatible" -// in Node.js -graph.getByLocation(__dirname); -``` - -```ts title="ESM compatible" -graph.getByLocation(import.meta.url); -``` - -**Parameters:** - -| Parameter | Type | Description | -| ---------- | -------- | ------------------------------- | -| `location` | `string` | A string or URL-based filepath. | - -**Returns:** [`Workspace`](#workspace) - -###### Throws - -`Error` if no Workspace can be found. - -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) - -##### getByName() - -```ts -getByName(name): Workspace -``` - -Get a [\`Workspace\`](#workspace) by string name. - -```ts -const workspace = graph.getByName('my-cool-package'); -``` - -**Parameters:** - -| Parameter | Type | Description | -| --------- | -------- | ------------------------------------------------------------------------- | -| `name` | `string` | A Workspace’s [\`name\`](#name) or any available [\`aliases\`](#aliases). | - -**Returns:** [`Workspace`](#workspace) - -###### Throws - -`Error` if no Workspace exists with the given input `name`. - -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) - ---- - -### Workspace - -#### Accessors - -##### aliases - -###### Get Signature - -```ts -get aliases(): string[] -``` - -Allow custom array of aliases. -If the fully qualified package name is scoped, this will include the un-scoped name - -**Returns:** `string`[] -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -##### codeowners - -###### Get Signature - -```ts -get codeowners(): Required> -``` - -**Returns:** `Required`\<`Record`\<`string`, `string`[]\>\> -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -##### config - -###### Get Signature - -```ts -get config(): Required -``` - -Get the Workspace's configuration - -**Returns:** `Required`\<[`RootConfig`](#rootconfigcustomlifecycles) \| [`WorkspaceConfig`](#workspaceconfigcustomlifecycles)\> -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -##### dependencies - -###### Get Signature +| Type Parameter | +| -------------- | +| `T` | -```ts -get dependencies(): Record -``` +**Parameters:** -Get the `package.json` defined production dependencies for the Workspace. +| Parameter | Type | +| --------------- | ------------------------------------------------------ | +| `options` | \{ `name`: `string`; `step`: [`LogStep`](#logstep); \} | +| `options.name` | `string` | +| `options.step`? | [`LogStep`](#logstep) | +| `fn` | (`step`) => `Promise`\<`T`\> | -**Returns:** `Record`\<`string`, `string`\> +**Returns:** `Promise`\<`T`\> +**Defined in:** [modules/logger/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/index.ts) -Map of modules to their version. +--- -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) +### Logger -##### description +The oneRepo logger helps build commands and capture output from spawned subprocess in a way that's both delightful to the end user and includes easy to scan and follow output. -###### Get Signature +All output will be redirected from `stdout` to `stderr` to ensure order of output and prevent confusion of what output can be piped and written to files. -```ts -get description(): undefined | string -``` +If the current terminal is a TTY, output will be buffered and asynchronous steps will animated with a progress logger. -Canonical to the `package.json` `"description"` field. +See [\`HandlerExtra\`](#handlerextra) for access the the global Logger instance. -**Returns:** `undefined` \| `string` -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) +#### Accessors -##### devDependencies +##### captureAll ###### Get Signature ```ts -get devDependencies(): Record +get captureAll(): boolean ``` -Get the `package.json` defined development dependencies for the Workspace. - -**Returns:** `Record`\<`string`, `string`\> - -Map of modules to their version. +**Experimental** +**Returns:** `boolean` -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -##### isRoot +##### hasError ###### Get Signature ```ts -get isRoot(): boolean +get hasError(): boolean ``` -Whether or not this Workspace is the root of the repository / Graph. +Whether or not an error has been sent to the logger or any of its steps. This is not necessarily indicative of uncaught thrown errors, but solely on whether `.error()` has been called in the `Logger` or any `Step` instance. **Returns:** `boolean` -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -##### location - -###### Get Signature - -```ts -get location(): string -``` - -Absolute path on the current filesystem to the Workspace. - -**Returns:** `string` -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -##### main - -###### Get Signature - -```ts -get main(): string -``` - -**Returns:** `string` -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -##### name - -###### Get Signature - -```ts -get name(): string -``` - -The full `name` of the Workspace, as defined in its `package.json` - -**Returns:** `string` -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -##### packageJson - -###### Get Signature - -```ts -get packageJson(): PackageJson -``` - -A full deep copy of the `package.json` file for the Workspace. Modifications to this object will not be preserved on the Workspace. - -**Returns:** [`PackageJson`](#packagejson-1) -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -##### peerDependencies - -###### Get Signature - -```ts -get peerDependencies(): Record -``` - -Get the `package.json` defined peer dependencies for the Workspace. - -**Returns:** `Record`\<`string`, `string`\> - -Map of modules to their version. - -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -##### private +##### hasInfo ###### Get Signature ```ts -get private(): boolean +get hasInfo(): boolean ``` -If a Workspace `package.json` is set to `private: true`, it will not be available to publish through NPM or other package management registries. +Whether or not an info message has been sent to the logger or any of its steps. **Returns:** `boolean` -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -##### publishablePackageJson - -###### Get Signature - -```ts -get publishablePackageJson(): null | PublicPackageJson -``` - -Get a version of the Workspace's `package.json` that is meant for publishing. - -This strips off `devDependencies` and applies appropriate [\`publishConfig\`](#publishconfig) values to the root of the `package.json`. This feature enables your monorepo to use source-dependencies and avoid manually building shared Workspaces for every change in order to see them take affect in dependent Workspaces. - -To take advantage of this, configure your `package.json` root level to point to source files and the `publishConfig` entries to point to the build location of those entrypoints. - -```json collapse={2-4} -{ - "name": "my-module", - "license": "MIT", - "type": "module", - "main": "./src/index.ts", - "publishConfig": { - "access": "public", - "main": "./dist/index.js", - "typings": "./dist/index.d.ts" - } -} -``` - -**Returns:** `null` \| [`PublicPackageJson`](#publicpackagejson) -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -##### scope +##### hasLog ###### Get Signature ```ts -get scope(): string +get hasLog(): boolean ``` -Get module name scope if there is one, eg `@onerepo` +Whether or not a log message has been sent to the logger or any of its steps. -**Returns:** `string` -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) +**Returns:** `boolean` +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -##### tasks +##### hasWarning ###### Get Signature ```ts -get tasks(): Partial> +get hasWarning(): boolean ``` -Get the task configuration as defined in the `onerepo.config.js` file at the root of the Workspace. - -**Returns:** `Partial`\<`Record`\<[`Lifecycle`](#lifecycle), [`Tasks`](#tasks)\>\> - -If a config does not exist, an empty object will be given. +Whether or not a warning has been sent to the logger or any of its steps. -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) +**Returns:** `boolean` +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -##### version +##### verbosity ###### Get Signature ```ts -get version(): undefined | string -``` - -**Returns:** `undefined` \| `string` -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -#### Methods - -##### getCodeowners() - -```ts -getCodeowners(filepath): string[] -``` - -**Parameters:** - -| Parameter | Type | -| ---------- | -------- | -| `filepath` | `string` | - -**Returns:** `string`[] -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -##### getTasks() - -```ts -getTasks(lifecycle): Required +get verbosity(): Verbosity ``` -Get a list of Workspace tasks for the given lifecycle - -**Parameters:** - -| Parameter | Type | -| ----------- | -------- | -| `lifecycle` | `string` | - -**Returns:** `Required`\<[`Tasks`](#tasks)\> -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) - -##### relative() +Get the logger's verbosity level -```ts -relative(to): string -``` +**Returns:** [`Verbosity`](#verbosity-2) -Get the relative path of an absolute path to the Workspace’s location root +###### Set Signature ```ts -const relativePath = workspace.relative('/some/absolute/path'); +set verbosity(value): void ``` -**Parameters:** +Applies the new verbosity to the main logger and any future steps. -| Parameter | Type | Description | -| --------- | -------- | ----------------- | -| `to` | `string` | Absolute filepath | +**Parameters:** -**Returns:** `string` +| Parameter | Type | +| --------- | --------------------------- | +| `value` | [`Verbosity`](#verbosity-2) | -Relative path to the workspace’s root location. +**Returns:** `void` +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) +##### writable -##### resolve() +###### Get Signature ```ts -resolve(...pathSegments): string +get writable(): boolean ``` -Resolve a full filepath within the Workspace given the path segments. Similar to Node.js's [path.resolve()](https://nodejs.org/dist/latest-v18.x/docs/api/path.html#pathresolvepaths). +**Returns:** `boolean` +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) + +#### Methods + +##### createStep() ```ts -const main = workspace.resolve(workspace.main); +createStep(name, opts?): LogStep ``` -**Parameters:** - -| Parameter | Type | Description | -| ----------------- | ---------- | ------------------------------------ | -| ...`pathSegments` | `string`[] | A sequence of paths or path segments | +Create a sub-step, [\`LogStep\`](#logstep), for the logger. This and any other step will be tracked and required to finish before exit. -**Returns:** `string` +```ts +const step = logger.createStep('Do fun stuff'); +// do some work +await step.end(); +``` -Absolute path based on the input path segments +**Parameters:** -**Defined in:** [modules/graph/src/Workspace.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Workspace.ts) +| Parameter | Type | Description | +| --------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | `string` | The name to be written and wrapped around any output logged to this new step. | +| `opts`? | \{ `description`: `string`; `writePrefixes`: `boolean`; \} | - | +| `opts.description`? | `string` | Optionally include extra information for performance tracing on this step. This description will be passed through to the [`performanceMark.detail`](https://nodejs.org/docs/latest-v20.x/api/perf_hooks.html#performancemarkdetail) recorded internally for this step. Use a [Performance Writer plugin](https://onerepo.tools/plugins/performance-writer/) to read and work with this detail. | +| `opts.writePrefixes`? | `boolean` | **Deprecated** This option no longer does anything and will be removed in v2.0.0 | ---- +**Returns:** [`LogStep`](#logstep) +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -### DependencyType +##### pause() ```ts -const DependencyType: { - DEV: 2; - PEER: 1; - PROD: 3; -}; +pause(): void ``` -#### Type declaration +When the terminal is a TTY, steps are automatically animated with a progress indicator. There are times when it's necessary to stop this animation, like when needing to capture user input from `stdin`. Call the `pause()` method before requesting input and [\`logger.unpause()\`](#unpause) when complete. -##### DEV +This process is also automated by the [\`run()\`](#run-1) function when `stdio` is set to `pipe`. ```ts -readonly DEV: 2; +logger.pause(); +// capture input +logger.unpause(); ``` -Development-only dependency (defined in `devDependencies` keys of `package.json`) +**Returns:** `void` +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -##### PEER +##### unpause() ```ts -readonly PEER: 1; +unpause(): void ``` -Peer dependency (defined in `peerDependencies` key of `package.json`) +Unpause the logger and resume writing buffered logs to the output stream. See [\`logger.pause()\`](#pause) for more information. -##### PROD +**Returns:** `void` +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) + +#### Logging + +##### debug() ```ts -readonly PROD: 3; +debug(contents): void ``` -Production dependency (defined in `dependencies` of `package.json`) - -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) +Extra debug logging when verbosity greater than or equal to 4. ---- +```ts +logger.debug('Log this content when verbosity is >= 4'); +``` -### DepType +If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged debug information: ```ts -type DepType: 1 | 2 | 3; +logger.debug(() => bigArray.map((item) => `- ${item.name}`).join('\n')); ``` -Dependency type value. +**Parameters:** -**See also:** -[\`DependencyType\`](#dependencytype) +| Parameter | Type | Description | +| ---------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. If a function is given with no arguments, the function will be executed and its response will be stringified for output. | -**Defined in:** [modules/graph/src/Graph.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/graph/src/Graph.ts) +**Returns:** `void` +**See also:** +[\`debug()\`](#debug-1) This is a pass-through for the main step’s [\`debug()\`](#debug-1) method. ---- +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -### GraphSchemaValidators +##### error() ```ts -type GraphSchemaValidators: Record Schema & { - $required: boolean; -}>>; +error(contents): void ``` -Definition for `graph verify` JSON schema validators. - -See [“Validating configurations”](/core/graph/#verifying-configurations) for more examples and use cases. +Log an error. This will cause the root logger to include an error and fail a command. ```ts -import type { GraphSchemaValidators } from 'onerepo'; +logger.error('Log this content when verbosity is >= 1'); +``` -export default { - '**': { - 'package.json': { - type: 'object', - $required: true, - properties: { - name: { type: 'string' }, - }, - required: ['name'], - }, - }, -} satisfies GraphSchemaValidators; +If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged error: + +```ts +logger.error(() => bigArray.map((item) => `- ${item.name}`).join('\n')); ``` -**Defined in:** [modules/onerepo/src/core/graph/schema.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/onerepo/src/core/graph/schema.ts) +**Parameters:** -## Logger +| Parameter | Type | Description | +| ---------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. If a function is given with no arguments, the function will be executed and its response will be stringified for output. | -### bufferSubLogger() +**Returns:** `void` +**See also:** +[\`error()\`](#error-1) This is a pass-through for the main step’s [\`error()\`](#error-1) method. + +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) + +##### info() ```ts -function bufferSubLogger(step): { - end: () => Promise; - logger: Logger; -}; +info(contents): void ``` -**Alpha** +Should be used to convey information or instructions through the log, will log when verbositu >= 1 -Create a new Logger instance that has its output buffered up to a LogStep. +```ts +logger.info('Log this content when verbosity is >= 1'); +``` + +If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: ```ts -const step = logger.createStep(name, { writePrefixes: false }); -const subLogger = bufferSubLogger(step); -const substep = subLogger.logger.createStep('Sub-step'); -substep.warning('This gets buffered'); -await substep.end(); -await subLogger.end(); -await step.en(); +logger.info(() => bigArray.map((item) => `- ${item.name}`).join('\n')); ``` **Parameters:** -| Parameter | Type | -| --------- | --------------------- | -| `step` | [`LogStep`](#logstep) | +| Parameter | Type | Description | +| ---------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. If a function is given with no arguments, the function will be executed and its response will be stringified for output. | -**Returns:** ```ts -{ -end: () => Promise; -logger: Logger; -} +**Returns:** `void` +**See also:** +[\`info()\`](#info-1) This is a pass-through for the main step’s [\`info()\`](#info-1) method. -```` +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -##### end() +##### log() ```ts -end: () => Promise; -```` - -**Returns:** `Promise`\<`void`\> +log(contents): void +``` -##### logger +General logging information. Useful for light informative debugging. Recommended to use sparingly. ```ts -logger: Logger; +logger.log('Log this content when verbosity is >= 3'); ``` -**Defined in:** [modules/logger/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/index.ts) - ---- - -### getLogger() +If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: ```ts -function getLogger(opts?): Logger; +logger.log(() => bigArray.map((item) => `- ${item.name}`).join('\n')); ``` -This gets the logger singleton for use across all of oneRepo and its commands. +**Parameters:** -Available directly as [\`HandlerExtra\`](#handlerextra) on [\`Handler\`](#handlercommandargv) functions: +| Parameter | Type | Description | +| ---------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. If a function is given with no arguments, the function will be executed and its response will be stringified for output. | + +**Returns:** `void` +**See also:** +[\`log()\`](#log-1) This is a pass-through for the main step’s [\`log()\`](#log-1) method. + +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) + +##### timing() ```ts -export const handler: Handler = (argv, { logger }) => { - logger.log('Hello!'); -}; +timing(start, end): void ``` +Log timing information between two [Node.js performance mark names](https://nodejs.org/dist/latest-v18.x/docs/api/perf_hooks.html#performancemarkname-options). + **Parameters:** -| Parameter | Type | -| --------- | ---------------------------------------------- | -| `opts`? | `Partial`\<[`LoggerOptions`](#loggeroptions)\> | +| Parameter | Type | Description | +| --------- | -------- | ------------------------------ | +| `start` | `string` | A `PerformanceMark` entry name | +| `end` | `string` | A `PerformanceMark` entry name | -**Returns:** [`Logger`](#logger) -**Defined in:** [modules/logger/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/index.ts) +**Returns:** `void` +**See also:** +[\`timing()\`](#timing-1) This is a pass-through for the main step’s [\`timing()\`](#timing-1) method. ---- +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -### stepWrapper() +##### warn() ```ts -function stepWrapper(options, fn): Promise; +warn(contents): void ``` -For cases where multiple processes need to be completed, but should be joined under a single [\`LogStep\`](#logstep) to avoid too much noisy output, this safely wraps an asynchronous function and handles step creation and completion, unless a `step` override is given. +Log a warning. Does not have any effect on the command run, but will be called out. ```ts -export async function exists(filename: string, { step }: Options = {}) { - return stepWrapper({ step, name: 'Step fallback name' }, (step) => { - return; // do some work - }); -} +logger.warn('Log this content when verbosity is >= 2'); ``` -#### Type Parameters +If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged warning: -| Type Parameter | -| -------------- | -| `T` | +```ts +logger.warn(() => bigArray.map((item) => `- ${item.name}`).join('\n')); +``` **Parameters:** -| Parameter | Type | -| --------------- | ------------------------------------------------------ | -| `options` | \{ `name`: `string`; `step`: [`LogStep`](#logstep); \} | -| `options.name` | `string` | -| `options.step`? | [`LogStep`](#logstep) | -| `fn` | (`step`) => `Promise`\<`T`\> | +| Parameter | Type | Description | +| ---------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. If a function is given with no arguments, the function will be executed and its response will be stringified for output. | + +**Returns:** `void` +**See also:** +[\`warn()\`](#warn-1) This is a pass-through for the main step’s [\`warn()\`](#warn-1) method. + +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) + +##### write() + +```ts +write( + chunk, + encoding?, + cb?): boolean +``` -**Returns:** `Promise`\<`T`\> -**Defined in:** [modules/logger/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/index.ts) +Write directly to the Logger's output stream, bypassing any formatting and verbosity filtering. ---- +:::caution[Advanced] +Since [LogStep](#logstep) implements a [Node.js duplex stream](https://nodejs.org/docs/latest-v20.x/api/stream.html#class-streamduplex), it is possible to use internal `write`, `read`, `pipe`, and all other available methods, but may not be fully recommended. +::: -### Logger +**Parameters:** -The oneRepo logger helps build commands and capture output from spawned subprocess in a way that's both delightful to the end user and includes easy to scan and follow output. +| Parameter | Type | +| ----------- | ------------------- | +| `chunk` | `any` | +| `encoding`? | `BufferEncoding` | +| `cb`? | (`error`) => `void` | -All output will be redirected from `stdout` to `stderr` to ensure order of output and prevent confusion of what output can be piped and written to files. +**Returns:** `boolean` +**See also:** +[\`LogStep.write\`](#write-1). -If the current terminal is a TTY, output will be buffered and asynchronous steps will animated with a progress logger. +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) -See [\`HandlerExtra\`](#handlerextra) for access the the global Logger instance. +--- -#### Accessors +### LogStep -##### captureAll +LogSteps are an enhancement of [Node.js duplex streams](https://nodejs.org/docs/latest-v20.x/api/stream.html#class-streamduplex) that enable writing contextual messages to the program's output. -###### Get Signature +Always create steps using the [\`logger.createStep()\`](#createstep) method so that they are properly tracked and linked to the parent logger. Creating a LogStep directly may result in errors and unintentional side effects. ```ts -get captureAll(): boolean +const myStep = logger.createStep(); +// Do work +myStep.info('Did some work'); +myStep.end(); ``` -**Experimental** -**Returns:** `boolean` +#### Extends -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +- `Duplex` + +#### Accessors ##### hasError @@ -6898,10 +2452,10 @@ get captureAll(): boolean get hasError(): boolean ``` -Whether or not an error has been sent to the logger or any of its steps. This is not necessarily indicative of uncaught thrown errors, but solely on whether `.error()` has been called in the `Logger` or any `Step` instance. +Whether this step has logged an error message. **Returns:** `boolean` -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) ##### hasInfo @@ -6911,10 +2465,10 @@ Whether or not an error has been sent to the logger or any of its steps. This is get hasInfo(): boolean ``` -Whether or not an info message has been sent to the logger or any of its steps. +Whether this step has logged an info-level message. **Returns:** `boolean` -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) ##### hasLog @@ -6924,10 +2478,10 @@ Whether or not an info message has been sent to the logger or any of its steps. get hasLog(): boolean ``` -Whether or not a log message has been sent to the logger or any of its steps. +Whether this step has logged a log-level message. **Returns:** `boolean` -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) ##### hasWarning @@ -6937,107 +2491,53 @@ Whether or not a log message has been sent to the logger or any of its steps. get hasWarning(): boolean ``` -Whether or not a warning has been sent to the logger or any of its steps. +Whether this step has logged a warning message. **Returns:** `boolean` -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) ##### verbosity -###### Get Signature - -```ts -get verbosity(): Verbosity -``` - -Get the logger's verbosity level - -**Returns:** [`Verbosity`](#verbosity-2) - ###### Set Signature ```ts -set verbosity(value): void +set verbosity(verbosity): void ``` -Recursively applies the new verbosity to the logger and all of its active steps. - **Parameters:** -| Parameter | Type | -| --------- | --------------------------- | -| `value` | [`Verbosity`](#verbosity-2) | +| Parameter | Type | +| ----------- | --------------------------- | +| `verbosity` | [`Verbosity`](#verbosity-2) | **Returns:** `void` -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) - -##### writable - -###### Get Signature - -```ts -get writable(): boolean -``` - -**Returns:** `boolean` -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) #### Methods -##### createStep() - -```ts -createStep(name, opts?): LogStep -``` - -Create a sub-step, [\`LogStep\`](#logstep), for the logger. This and any other step will be tracked and required to finish before exit. - -```ts -const step = logger.createStep('Do fun stuff'); -// do some work -await step.end(); -``` - -**Parameters:** - -| Parameter | Type | Description | -| --------------------- | --------------------------------- | ----------------------------------------------------------------------------- | -| `name` | `string` | The name to be written and wrapped around any output logged to this new step. | -| `opts`? | \{ `writePrefixes`: `boolean`; \} | - | -| `opts.writePrefixes`? | `boolean` | - | - -**Returns:** [`LogStep`](#logstep) -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) - -##### pause() +##### end() ```ts -pause(): void +end(): this ``` -When the terminal is a TTY, steps are automatically animated with a progress indicator. There are times when it's necessary to stop this animation, like when needing to capture user input from `stdin`. Call the `pause()` method before requesting input and [\`logger.unpause()\`](#unpause) when complete. +Signal the end of this step. After this method is called, it will no longer accept any more logs of any variety and will be removed from the parent Logger's queue. -This process is also automated by the [\`run()\`](#run-1) function when `stdio` is set to `pipe`. +Failure to call this method will result in a warning and potentially fail oneRepo commands. It is important to ensure that each step is cleanly ended before returning from commands. ```ts -logger.pause(); -// capture input -logger.unpause(); +const myStep = logger.createStep('My step'); +// do work +myStep.end(); ``` -**Returns:** `void` -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) - -##### unpause() +**Returns:** `this` -```ts -unpause(): void -``` +###### Overrides -Unpause the logger and resume writing buffered logs to the output stream. See [\`logger.pause()\`](#pause-1) for more information. +`Duplex.end` -**Returns:** `void` -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) #### Logging @@ -7047,29 +2547,28 @@ Unpause the logger and resume writing buffered logs to the output stream. See [\ debug(contents): void ``` -Extra debug logging when verbosity greater than or equal to 4. +Log a debug message for this step. Debug messages will only be written to the program output if the [\`verbosity\`](#verbosity) is set to 4 or greater. ```ts -logger.debug('Log this content when verbosity is >= 4'); +const step = logger.createStep('My step'); +step.debug('This message will be recorded and written out as an "DBG" labeled message'); +step.end(); ``` If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged debug information: ```ts -logger.debug(() => bigArray.map((item) => item.name)); +step.debug(() => bigArray.map((item) => `- ${item.name}`).join('\n')); ``` **Parameters:** -| Parameter | Type | Description | -| ---------- | --------- | -------------------------------------------------------------------- | -| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. | +| Parameter | Type | Description | +| ---------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `contents` | `unknown` | Any value may be logged as a debug message, but will be stringified upon output. If a function is given with no arguments, the function will be executed and its response will be stringified for output. | **Returns:** `void` -**See also:** -[\`debug()\`](#debug) This is a pass-through for the main step’s [\`debug()\`](#debug) method. - -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) ##### error() @@ -7077,29 +2576,28 @@ logger.debug(() => bigArray.map((item) => item.name)); error(contents): void ``` -Log an error. This will cause the root logger to include an error and fail a command. +Log an error message for this step. Any error log will cause the entire command run in oneRepo to fail and exit with code `1`. Error messages will only be written to the program output if the [\`verbosity\`](#verbosity) is set to 1 or greater – even if not written, the command will still fail and include an exit code. ```ts -logger.error('Log this content when verbosity is >= 1'); +const step = logger.createStep('My step'); +step.error('This message will be recorded and written out as an "ERR" labeled message'); +step.end(); ``` If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged error: ```ts -logger.error(() => bigArray.map((item) => item.name)); +step.error(() => bigArray.map((item) => `- ${item.name}`).join('\n')); ``` **Parameters:** -| Parameter | Type | Description | -| ---------- | --------- | -------------------------------------------------------------------- | -| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. | +| Parameter | Type | Description | +| ---------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `contents` | `unknown` | Any value may be logged as an error, but will be stringified upon output. If a function is given with no arguments, the function will be executed and its response will be stringified for output. | **Returns:** `void` -**See also:** -[\`error()\`](#error) This is a pass-through for the main step’s [\`error()\`](#error) method. - -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) ##### info() @@ -7107,29 +2605,28 @@ logger.error(() => bigArray.map((item) => item.name)); info(contents): void ``` -Should be used to convey information or instructions through the log, will log when verbositu >= 1 +Log an informative message for this step. Info messages will only be written to the program output if the [\`verbosity\`](#verbosity) is set to 1 or greater. ```ts -logger.info('Log this content when verbosity is >= 1'); +const step = logger.createStep('My step'); +step.info('This message will be recorded and written out as an "INFO" labeled message'); +step.end(); ``` If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: ```ts -logger.info(() => bigArray.map((item) => item.name)); +step.info(() => bigArray.map((item) => `- ${item.name}`).join('\n')); ``` **Parameters:** -| Parameter | Type | Description | -| ---------- | --------- | -------------------------------------------------------------------- | -| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. | +| Parameter | Type | Description | +| ---------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `contents` | `unknown` | Any value may be logged as info, but will be stringified upon output. If a function is given with no arguments, the function will be executed and its response will be stringified for output. | **Returns:** `void` -**See also:** -[\`info()\`](#info) This is a pass-through for the main step’s [\`info()\`](#info) method. - -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) ##### log() @@ -7137,29 +2634,28 @@ logger.info(() => bigArray.map((item) => item.name)); log(contents): void ``` -General logging information. Useful for light informative debugging. Recommended to use sparingly. +Log a message for this step. Log messages will only be written to the program output if the [\`verbosity\`](#verbosity) is set to 3 or greater. ```ts -logger.log('Log this content when verbosity is >= 3'); +const step = logger.createStep('My step'); +step.log('This message will be recorded and written out as an "LOG" labeled message'); +step.end(); ``` If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: ```ts -logger.log(() => bigArray.map((item) => item.name)); +step.log(() => bigArray.map((item) => `- ${item.name}`).join('\n')); ``` **Parameters:** -| Parameter | Type | Description | -| ---------- | --------- | -------------------------------------------------------------------- | -| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. | +| Parameter | Type | Description | +| ---------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `contents` | `unknown` | Any value may be logged, but will be stringified upon output. If a function is given with no arguments, the function will be executed and its response will be stringified for output. | **Returns:** `void` -**See also:** -[\`log()\`](#log) This is a pass-through for the main step’s [\`log()\`](#log) method. - -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) ##### timing() @@ -7167,20 +2663,28 @@ logger.log(() => bigArray.map((item) => item.name)); timing(start, end): void ``` -Log timing information between two [Node.js performance mark names](https://nodejs.org/dist/latest-v18.x/docs/api/perf_hooks.html#performancemarkname-options). +Log extra performance timing information. + +Timing information will only be written to the program output if the [\`verbosity\`](#verbosity) is set to 5. + +```ts +const myStep = logger.createStep('My step'); +performance.mark('start'); +// do work +performance.mark('end'); +myStep.timing('start', 'end'); +myStep.end(); +``` **Parameters:** -| Parameter | Type | Description | -| --------- | -------- | ------------------------------ | -| `start` | `string` | A `PerformanceMark` entry name | -| `end` | `string` | A `PerformanceMark` entry name | +| Parameter | Type | +| --------- | -------- | +| `start` | `string` | +| `end` | `string` | **Returns:** `void` -**See also:** -[\`timing()\`](#timing) This is a pass-through for the main step’s [\`timing()\`](#timing) method. - -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) ##### warn() @@ -7188,29 +2692,59 @@ Log timing information between two [Node.js performance mark names](https://node warn(contents): void ``` -Log a warning. Does not have any effect on the command run, but will be called out. +Log a warning message for this step. Warnings will _not_ cause oneRepo commands to fail. Warning messages will only be written to the program output if the [\`verbosity\`](#verbosity) is set to 2 or greater. ```ts -logger.warn('Log this content when verbosity is >= 2'); +const step = logger.createStep('My step'); +step.warn('This message will be recorded and written out as a "WRN" labeled message'); +step.end(); ``` If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged warning: ```ts -logger.warn(() => bigArray.map((item) => item.name)); +step.warn(() => bigArray.map((item) => `- ${item.name}`).join('\n')); ``` **Parameters:** -| Parameter | Type | Description | -| ---------- | --------- | -------------------------------------------------------------------- | -| `contents` | `unknown` | Any value that can be converted to a string for writing to `stderr`. | +| Parameter | Type | Description | +| ---------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `contents` | `unknown` | Any value may be logged as a warning, but will be stringified upon output. If a function is given with no arguments, the function will be executed and its response will be stringified for output. | **Returns:** `void` -**See also:** -[\`warn()\`](#warn) This is a pass-through for the main step’s [\`warn()\`](#warn) method. +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) -**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +##### write() + +```ts +write( + chunk, + encoding?, + cb?): boolean +``` + +Write directly to the step's stream, bypassing any formatting and verbosity filtering. + +:::caution[Advanced] +Since [LogStep](#logstep) implements a [Node.js duplex stream](https://nodejs.org/docs/latest-v20.x/api/stream.html#class-streamduplex), it is possible to use internal `write`, `read`, `pipe`, and all other available methods, but may not be fully recommended. +::: + +**Parameters:** + +| Parameter | Type | +| ----------- | ------------------- | +| `chunk` | `any` | +| `encoding`? | `BufferEncoding` | +| `cb`? | (`error`) => `void` | + +**Returns:** `boolean` + +###### Overrides + +`Duplex.write` + +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) --- @@ -7254,6 +2788,48 @@ Control how much and what kind of output the Logger will provide. --- +### LogStepOptions + +```ts +type LogStepOptions: { + description: string; + name: string; + verbosity: Verbosity; +}; +``` + +#### Type declaration + +##### description? + +```ts +optional description: string; +``` + +Optionally include extra information for performance tracing on this step. This description will be passed through to the [`performanceMark.detail`](https://nodejs.org/docs/latest-v20.x/api/perf_hooks.html#performancemarkdetail) recorded internally for this step. + +Use a [Performance Writer plugin](https://onerepo.tools/plugins/performance-writer/) to read and work with this detail. + +##### name + +```ts +name: string; +``` + +Wraps all step output within the name provided for the step. + +##### verbosity + +```ts +verbosity: Verbosity; +``` + +The verbosity for this step, inherited from its parent [Logger](#logger). + +**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) + +--- + ### Verbosity ```ts @@ -7956,40 +3532,9 @@ new BatchError(errors, options?): BatchError #### Properties -| Property | Modifier | Type | Description | Inherited from | Defined in | -| -------------------- | -------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| `cause?` | `public` | `unknown` | - | `Error.cause` | node_modules/typescript/lib/lib.es2022.error.d.ts:26 | -| `errors` | `public` | (`string` \| [`SubprocessError`](#subprocesserror))[] | - | - | [modules/subprocess/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/subprocess/src/index.ts) | -| `message` | `public` | `string` | - | `Error.message` | node_modules/typescript/lib/lib.es5.d.ts:1077 | -| `name` | `public` | `string` | - | `Error.name` | node_modules/typescript/lib/lib.es5.d.ts:1076 | -| `prepareStackTrace?` | `static` | (`err`: `Error`, `stackTraces`: `CallSite`[]) => `any` | Optional override for formatting stack traces **See** https://v8.dev/docs/stack-trace-api#customizing-stack-traces | `Error.prepareStackTrace` | node_modules/@types/node/globals.d.ts:98 | -| `stack?` | `public` | `string` | - | `Error.stack` | node_modules/typescript/lib/lib.es5.d.ts:1078 | -| `stackTraceLimit` | `static` | `number` | - | `Error.stackTraceLimit` | node_modules/@types/node/globals.d.ts:100 | - -#### Methods - -##### captureStackTrace() - -```ts -static captureStackTrace(targetObject, constructorOpt?): void -``` - -Create .stack property on a target object - -**Parameters:** - -| Parameter | Type | -| ----------------- | ---------- | -| `targetObject` | `object` | -| `constructorOpt`? | `Function` | - -**Returns:** `void` - -###### Inherited from - -`Error.captureStackTrace` - -**Defined in:** node_modules/@types/node/globals.d.ts:91 +| Property | Type | Defined in | +| -------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `errors` | (`string` \| [`SubprocessError`](#subprocesserror))[] | [modules/subprocess/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/subprocess/src/index.ts) | --- @@ -8022,42 +3567,6 @@ new SubprocessError(message, options?): SubprocessError **Defined in:** [modules/subprocess/src/index.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/subprocess/src/index.ts) -#### Properties - -| Property | Modifier | Type | Description | Inherited from | Defined in | -| -------------------- | -------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | ------------------------- | ---------------------------------------------------- | -| `cause?` | `public` | `unknown` | - | `Error.cause` | node_modules/typescript/lib/lib.es2022.error.d.ts:26 | -| `message` | `public` | `string` | - | `Error.message` | node_modules/typescript/lib/lib.es5.d.ts:1077 | -| `name` | `public` | `string` | - | `Error.name` | node_modules/typescript/lib/lib.es5.d.ts:1076 | -| `prepareStackTrace?` | `static` | (`err`: `Error`, `stackTraces`: `CallSite`[]) => `any` | Optional override for formatting stack traces **See** https://v8.dev/docs/stack-trace-api#customizing-stack-traces | `Error.prepareStackTrace` | node_modules/@types/node/globals.d.ts:98 | -| `stack?` | `public` | `string` | - | `Error.stack` | node_modules/typescript/lib/lib.es5.d.ts:1078 | -| `stackTraceLimit` | `static` | `number` | - | `Error.stackTraceLimit` | node_modules/@types/node/globals.d.ts:100 | - -#### Methods - -##### captureStackTrace() - -```ts -static captureStackTrace(targetObject, constructorOpt?): void -``` - -Create .stack property on a target object - -**Parameters:** - -| Parameter | Type | -| ----------------- | ---------- | -| `targetObject` | `object` | -| `constructorOpt`? | `Function` | - -**Returns:** `void` - -###### Inherited from - -`Error.captureStackTrace` - -**Defined in:** node_modules/@types/node/globals.d.ts:91 - --- ### BatchOptions diff --git a/modules/logger/src/LogStep.ts b/modules/logger/src/LogStep.ts index a64d21b8..7d0b6699 100644 --- a/modules/logger/src/LogStep.ts +++ b/modules/logger/src/LogStep.ts @@ -3,25 +3,62 @@ import pc from 'picocolors'; import { stringify } from './utils/string'; import type { LineType, LoggedBuffer, Verbosity } from './types'; +/** + * @group Logger + */ export type LogStepOptions = { + /** + * Wraps all step output within the name provided for the step. + */ name: string; + /** + * Optionally include extra information for performance tracing on this step. This description will be passed through to the [`performanceMark.detail`](https://nodejs.org/docs/latest-v20.x/api/perf_hooks.html#performancemarkdetail) recorded internally for this step. + * + * Use a [Performance Writer plugin](https://onerepo.tools/plugins/performance-writer/) to read and work with this detail. + */ description?: string; + /** + * The verbosity for this step, inherited from its parent {@link Logger}. + */ verbosity: Verbosity; }; +/** + * LogSteps are an enhancement of [Node.js duplex streams](https://nodejs.org/docs/latest-v20.x/api/stream.html#class-streamduplex) that enable writing contextual messages to the program's output. + * + * Always create steps using the {@link Logger.createStep | `logger.createStep()`} method so that they are properly tracked and linked to the parent logger. Creating a LogStep directly may result in errors and unintentional side effects. + * + * ```ts + * const myStep = logger.createStep(); + * // Do work + * myStep.info('Did some work'); + * myStep.end(); + * ``` + * @group Logger + */ export class LogStep extends Duplex { + /** + * @internal + */ name?: string; + /** + * @internal + */ + isPiped: boolean = false; + #startMark: string; #verbosity: Verbosity; - isPiped: boolean = false; - #hasError: boolean = false; #hasWarning: boolean = false; #hasInfo: boolean = false; #hasLog: boolean = false; - constructor({ description, name, verbosity }: LogStepOptions) { + /** + * @internal + */ + constructor(options: LogStepOptions) { + const { description, name, verbosity } = options; super({ decodeStrings: false }); this.#verbosity = verbosity; @@ -34,14 +71,60 @@ export class LogStep extends Duplex { this.#write('start', name); } + /** + * Write directly to the step's stream, bypassing any formatting and verbosity filtering. + * + * :::caution[Advanced] + * Since {@link LogStep} implements a [Node.js duplex stream](https://nodejs.org/docs/latest-v20.x/api/stream.html#class-streamduplex), it is possible to use internal `write`, `read`, `pipe`, and all other available methods, but may not be fully recommended. + * ::: + * + * @group Logging + */ + write( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + chunk: any, + encoding?: BufferEncoding, + cb?: (error: Error | null | undefined) => void, + ): boolean; + + /** + * @internal + */ + write( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + chunk: any, + cb?: (error: Error | null | undefined) => void, + ): boolean; + + write( + // @ts-expect-error + ...args + ) { + // @ts-expect-error + return super.write(...args); + } + + /** + * @internal + */ _read() {} - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _write(chunk: string | Buffer, encoding = 'utf8', callback: () => void) { + /** + * @internal + */ + _write( + chunk: string | Buffer, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + encoding = 'utf8', + callback: () => void, + ) { this.push(chunk); callback(); } + /** + * @internal + */ _final(callback: () => void) { this.push(null); callback(); @@ -64,62 +147,199 @@ export class LogStep extends Duplex { this.#verbosity = verbosity; } - set hasError(has: boolean) { - this.#hasError = this.#hasError || has; + /** + * @internal + */ + set hasError(hasError: boolean) { + this.#hasError = this.#hasError || hasError; } + /** + * Whether this step has logged an error message. + */ get hasError() { return this.#hasError; } - set hasWarning(has: boolean) { - this.#hasWarning = this.#hasWarning || has; + /** + * @internal + */ + set hasWarning(hasWarning: boolean) { + this.#hasWarning = this.#hasWarning || hasWarning; } + /** + * Whether this step has logged a warning message. + */ get hasWarning() { return this.#hasWarning; } - set hasInfo(has: boolean) { - this.#hasInfo = this.#hasInfo || has; + /** + * @internal + */ + set hasInfo(hasWarning: boolean) { + this.#hasInfo = this.#hasInfo || hasWarning; } + /** + * Whether this step has logged an info-level message. + */ get hasInfo() { return this.#hasInfo; } - set hasLog(has: boolean) { - this.#hasLog = this.#hasLog || has; + /** + * @internal + */ + set hasLog(hasLog: boolean) { + this.#hasLog = this.#hasLog || hasLog; } + /** + * Whether this step has logged a log-level message. + */ get hasLog() { return this.#hasLog; } + /** + * Log an error message for this step. Any error log will cause the entire command run in oneRepo to fail and exit with code `1`. Error messages will only be written to the program output if the {@link Logger.verbosity | `verbosity`} is set to 1 or greater – even if not written, the command will still fail and include an exit code. + * + * + * ```ts + * const step = logger.createStep('My step'); + * step.error('This message will be recorded and written out as an "ERR" labeled message'); + * step.end(); + * ``` + * + * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged error: + * + * ```ts + * step.error(() => bigArray.map((item) => `- ${item.name}`).join('\n')); + * ``` + * + * @param contents Any value may be logged as an error, but will be stringified upon output. If a function is given with no arguments, the function will be executed and its response will be stringified for output. + * + * @group Logging + */ error(contents: unknown) { this.#hasError = true; this.#write('error', contents); } + /** + * Log a warning message for this step. Warnings will _not_ cause oneRepo commands to fail. Warning messages will only be written to the program output if the {@link Logger.verbosity | `verbosity`} is set to 2 or greater. + * + * + * ```ts + * const step = logger.createStep('My step'); + * step.warn('This message will be recorded and written out as a "WRN" labeled message'); + * step.end(); + * ``` + * + * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged warning: + * + * ```ts + * step.warn(() => bigArray.map((item) => `- ${item.name}`).join('\n')); + * ``` + * + * @param contents Any value may be logged as a warning, but will be stringified upon output. If a function is given with no arguments, the function will be executed and its response will be stringified for output. + * + * @group Logging + */ warn(contents: unknown) { this.#hasWarning = true; this.#write('warn', contents); } + /** + * Log an informative message for this step. Info messages will only be written to the program output if the {@link Logger.verbosity | `verbosity`} is set to 1 or greater. + * + * ```ts + * const step = logger.createStep('My step'); + * step.info('This message will be recorded and written out as an "INFO" labeled message'); + * step.end(); + * ``` + * + * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: + * + * ```ts + * step.info(() => bigArray.map((item) => `- ${item.name}`).join('\n')); + * ``` + * + * @param contents Any value may be logged as info, but will be stringified upon output. If a function is given with no arguments, the function will be executed and its response will be stringified for output. + * + * @group Logging + */ info(contents: unknown) { this.#hasInfo = true; this.#write('info', contents); } + /** + * Log a message for this step. Log messages will only be written to the program output if the {@link Logger.verbosity | `verbosity`} is set to 3 or greater. + * + * ```ts + * const step = logger.createStep('My step'); + * step.log('This message will be recorded and written out as an "LOG" labeled message'); + * step.end(); + * ``` + * + * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: + * + * ```ts + * step.log(() => bigArray.map((item) => `- ${item.name}`).join('\n')); + * ``` + * + * @param contents Any value may be logged, but will be stringified upon output. If a function is given with no arguments, the function will be executed and its response will be stringified for output. + * + * @group Logging + */ log(contents: unknown) { this.#hasLog = true; this.#write('log', contents); } + /** + * Log a debug message for this step. Debug messages will only be written to the program output if the {@link Logger.verbosity | `verbosity`} is set to 4 or greater. + * + * ```ts + * const step = logger.createStep('My step'); + * step.debug('This message will be recorded and written out as an "DBG" labeled message'); + * step.end(); + * ``` + * + * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged debug information: + * + * ```ts + * step.debug(() => bigArray.map((item) => `- ${item.name}`).join('\n')); + * ``` + * + * @param contents Any value may be logged as a debug message, but will be stringified upon output. If a function is given with no arguments, the function will be executed and its response will be stringified for output. + * + * @group Logging + */ debug(contents: unknown) { this.#write('debug', contents); } + /** + * Log extra performance timing information. + * + * Timing information will only be written to the program output if the {@link Logger.verbosity | `verbosity`} is set to 5. + * + * ```ts + * const myStep = logger.createStep('My step'); + * performance.mark('start'); + * // do work + * performance.mark('end'); + * myStep.timing('start', 'end'); + * myStep.end(); + * ``` + * + * @group Logging + */ timing(start: string, end: string) { const [startMark] = performance.getEntriesByName(start); const [endMark] = performance.getEntriesByName(end); @@ -133,6 +353,17 @@ export class LogStep extends Duplex { ); } + /** + * Signal the end of this step. After this method is called, it will no longer accept any more logs of any variety and will be removed from the parent Logger's queue. + * + * Failure to call this method will result in a warning and potentially fail oneRepo commands. It is important to ensure that each step is cleanly ended before returning from commands. + * + * ```ts + * const myStep = logger.createStep('My step'); + * // do work + * myStep.end(); + * ``` + */ end() { // Makes calling `.end()` multiple times safe. // TODO: make this unnecessary diff --git a/modules/logger/src/Logger.ts b/modules/logger/src/Logger.ts index a5b42878..a04ed0d9 100644 --- a/modules/logger/src/Logger.ts +++ b/modules/logger/src/Logger.ts @@ -1,13 +1,12 @@ import type { Writable } from 'node:stream'; import { destroyCurrent, setCurrent } from './global'; +import type { LogStepOptions } from './LogStep'; import { LogStep } from './LogStep'; import { LogStepToString } from './transforms/LogStepToString'; import { LogProgress } from './transforms/LogProgress'; import { hideCursor, showCursor } from './utils/cursor'; import type { Verbosity } from './types'; -// EventEmitter.defaultMaxListeners = cpus().length + 2; - /** * @group Logger */ @@ -77,7 +76,7 @@ export class Logger { } /** - * Recursively applies the new verbosity to the logger and all of its active steps. + * Applies the new verbosity to the main logger and any future steps. */ set verbosity(value: Verbosity) { this.#verbosity = Math.max(0, value) as Verbosity; @@ -173,13 +172,20 @@ export class Logger { */ createStep( name: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars opts: { - // @deprecated This option no longer does anything + /** + * Optionally include extra information for performance tracing on this step. This description will be passed through to the [`performanceMark.detail`](https://nodejs.org/docs/latest-v20.x/api/perf_hooks.html#performancemarkdetail) recorded internally for this step. + * + * Use a [Performance Writer plugin](https://onerepo.tools/plugins/performance-writer/) to read and work with this detail. + */ + description?: string; + /** + * @deprecated This option no longer does anything and will be removed in v2.0.0 + */ writePrefixes?: boolean; } = {}, ) { - const step = new LogStep({ name, verbosity: this.#verbosity }); + const step = new LogStep({ name, verbosity: this.#verbosity, description: opts.description }); this.#steps.push(step); step.on('end', () => this.#onEnd(step)); @@ -187,6 +193,40 @@ export class Logger { return step; } + /** + * Write directly to the Logger's output stream, bypassing any formatting and verbosity filtering. + * + * :::caution[Advanced] + * Since {@link LogStep} implements a [Node.js duplex stream](https://nodejs.org/docs/latest-v20.x/api/stream.html#class-streamduplex), it is possible to use internal `write`, `read`, `pipe`, and all other available methods, but may not be fully recommended. + * ::: + * + * @group Logging + * @see {@link LogStep.write | `LogStep.write`}. + */ + write( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + chunk: any, + encoding?: BufferEncoding, + cb?: (error: Error | null | undefined) => void, + ): boolean; + + /** + * @internal + */ + write( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + chunk: any, + cb?: (error: Error | null | undefined) => void, + ): boolean; + + write( + // @ts-expect-error + ...args + ) { + // @ts-expect-error + return super.write(...args); + } + /** * Should be used to convey information or instructions through the log, will log when verbositu >= 1 * @@ -197,12 +237,11 @@ export class Logger { * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: * * ```ts - * logger.info(() => bigArray.map((item) => item.name)); + * logger.info(() => bigArray.map((item) => `- ${item.name}`).join('\n')); * ``` * - * * @group Logging - * @param contents Any value that can be converted to a string for writing to `stderr`. + * @param contents Any value that can be converted to a string for writing to `stderr`. If a function is given with no arguments, the function will be executed and its response will be stringified for output. * @see {@link LogStep#info | `info()`} This is a pass-through for the main step’s {@link LogStep#info | `info()`} method. */ info(contents: unknown) { @@ -220,11 +259,11 @@ export class Logger { * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged error: * * ```ts - * logger.error(() => bigArray.map((item) => item.name)); + * logger.error(() => bigArray.map((item) => `- ${item.name}`).join('\n')); * ``` * * @group Logging - * @param contents Any value that can be converted to a string for writing to `stderr`. + * @param contents Any value that can be converted to a string for writing to `stderr`. If a function is given with no arguments, the function will be executed and its response will be stringified for output. * @see {@link LogStep#error | `error()`} This is a pass-through for the main step’s {@link LogStep#error | `error()`} method. */ error(contents: unknown) { @@ -242,11 +281,11 @@ export class Logger { * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged warning: * * ```ts - * logger.warn(() => bigArray.map((item) => item.name)); + * logger.warn(() => bigArray.map((item) => `- ${item.name}`).join('\n')); * ``` * * @group Logging - * @param contents Any value that can be converted to a string for writing to `stderr`. + * @param contents Any value that can be converted to a string for writing to `stderr`. If a function is given with no arguments, the function will be executed and its response will be stringified for output. * @see {@link LogStep#warn | `warn()`} This is a pass-through for the main step’s {@link LogStep#warn | `warn()`} method. */ warn(contents: unknown) { @@ -264,11 +303,11 @@ export class Logger { * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged information: * * ```ts - * logger.log(() => bigArray.map((item) => item.name)); + * logger.log(() => bigArray.map((item) => `- ${item.name}`).join('\n')); * ``` * * @group Logging - * @param contents Any value that can be converted to a string for writing to `stderr`. + * @param contents Any value that can be converted to a string for writing to `stderr`. If a function is given with no arguments, the function will be executed and its response will be stringified for output. * @see {@link LogStep#log | `log()`} This is a pass-through for the main step’s {@link LogStep#log | `log()`} method. */ log(contents: unknown) { @@ -286,11 +325,11 @@ export class Logger { * If a function with zero arguments is passed, the function will be executed before writing. This is helpful for avoiding extra work in the event that the verbosity is not actually high enough to render the logged debug information: * * ```ts - * logger.debug(() => bigArray.map((item) => item.name)); + * logger.debug(() => bigArray.map((item) => `- ${item.name}`).join('\n')); * ``` * * @group Logging - * @param contents Any value that can be converted to a string for writing to `stderr`. + * @param contents Any value that can be converted to a string for writing to `stderr`. If a function is given with no arguments, the function will be executed and its response will be stringified for output. * @see {@link LogStep#debug | `debug()`} This is a pass-through for the main step’s {@link LogStep#debug | `debug()`} method. */ debug(contents: unknown) { @@ -365,10 +404,9 @@ export class Logger { if (step.isPiped) { return; - // step.unpipe(); } - // this.unpause(); + hideCursor(); if (!step.name || !(this.#stream as typeof process.stderr).isTTY) { step.pipe(new LogStepToString()).pipe(this.#stream); diff --git a/modules/logger/src/types.ts b/modules/logger/src/types.ts index d01e8d21..5275a746 100644 --- a/modules/logger/src/types.ts +++ b/modules/logger/src/types.ts @@ -14,8 +14,14 @@ */ export type Verbosity = 0 | 1 | 2 | 3 | 4 | 5; +/** + * @internal + */ export type LineType = 'start' | 'end' | 'error' | 'warn' | 'info' | 'log' | 'debug' | 'timing'; +/** + * @internal + */ export type LoggedBuffer = { type: LineType; contents: string; From b5b68849f40fd928f9b19dbeef41cafbda7eaa43 Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Mon, 8 Apr 2024 16:57:32 -0700 Subject: [PATCH 11/17] fix: buffer prefixing --- modules/logger/src/index.ts | 47 ++++--------------- .../logger/src/transforms/LogStepToString.ts | 2 +- 2 files changed, 11 insertions(+), 38 deletions(-) diff --git a/modules/logger/src/index.ts b/modules/logger/src/index.ts index ef31c382..e36b2eff 100644 --- a/modules/logger/src/index.ts +++ b/modules/logger/src/index.ts @@ -97,51 +97,18 @@ export async function stepWrapper( */ export function bufferSubLogger(step: LogStep): { logger: Logger; end: () => Promise } { const logger = getLogger(); - // const buffer = new LogStep({ name: '' }); const stream = new Buffered(); const subLogger = new Logger({ verbosity: logger.verbosity, stream, captureAll: true }); - // let activeStep: Buffer | undefined; - // function write(method: 'error' | 'info' | 'warn' | 'log' | 'debug', chunk: Buffer) { - // activeStep && step.error(() => activeStep?.toString().trimEnd()); - // activeStep = undefined; - // step[method](() => chunk.toString().trimEnd()); - // } - // function proxyChunks(chunk: Buffer) { - // if (chunk.toString().startsWith(' ┌')) { - // activeStep = chunk; - // } - - // if (chunk.toString().startsWith(' └')) { - // activeStep = undefined; - // } - - // if (subLogger.hasError) { - // write('error', chunk); - // } else if (subLogger.hasInfo) { - // write('info', chunk); - // } else if (subLogger.hasWarning) { - // write('warn', chunk); - // } else if (subLogger.hasLog) { - // write('log', chunk); - // } else { - // write('debug', chunk); - // } - // } stream.pipe(step); - // buffer.on('data', proxyChunks); return { logger: subLogger, async end() { - // buffer.unpipe(); - // buffer.off('data', proxyChunks); await new Promise((resolve) => { setImmediate(async () => { stream.unpipe(); stream.destroy(); - // await subLogger.end(); - // buffer.destroy(); resolve(); }); }); @@ -152,13 +119,19 @@ export function bufferSubLogger(step: LogStep): { logger: Logger; end: () => Pro export const restoreCursor = restoreCursorDefault; class Buffered extends Transform { - _transform(chunk: Buffer, encoding = 'utf8', callback: () => void) { + _transform( + chunk: Buffer, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + encoding = 'utf8', + callback: () => void, + ) { this.push( - chunk + `${chunk .toString() + .trim() .split('\n') - .map((line) => ` │${line}`) - .join('\n'), + .map((line) => (line.startsWith(prefix.end) ? `${line}` : ` │ ${line.trim()}`)) + .join('\n')}\n`, ); callback(); } diff --git a/modules/logger/src/transforms/LogStepToString.ts b/modules/logger/src/transforms/LogStepToString.ts index 754e159d..64b15d6c 100644 --- a/modules/logger/src/transforms/LogStepToString.ts +++ b/modules/logger/src/transforms/LogStepToString.ts @@ -35,7 +35,7 @@ export class LogStepToString extends Transform { return `${!group ? pc.bold(pc.dim('◼︎ ')) : prefix[type]}${hasError ? pc.red('✘') : pc.green('✔')} ${output}`; } if (type === 'start') { - return `${!group ? pc.bold(pc.dim('➤ ')) : prefix[type]}${output}`; + return `${!group ? '' : prefix[type]}${output}`; } return output .split('\n') From c2fe6ece4002247a495a028969225ea4b9d3441f Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Wed, 10 Apr 2024 18:28:57 -0700 Subject: [PATCH 12/17] checkpoint --- modules/logger/src/Logger.ts | 10 +- modules/logger/src/__tests__/LogStep.test.ts | 424 +++++++----------- modules/logger/src/__tests__/Logger.test.ts | 22 +- .../logger/src/transforms/LogStepToString.ts | 1 + 4 files changed, 175 insertions(+), 282 deletions(-) diff --git a/modules/logger/src/Logger.ts b/modules/logger/src/Logger.ts index a04ed0d9..9d4421e2 100644 --- a/modules/logger/src/Logger.ts +++ b/modules/logger/src/Logger.ts @@ -152,7 +152,7 @@ export class Logger { } /** - * Unpause the logger and resume writing buffered logs to the output stream. See {@link Logger#pause | `logger.pause()`} for more information. + * Unpause the logger and uncork writing buffered logs to the output stream. See {@link Logger#pause | `logger.pause()`} for more information. */ unpause() { this.#stream.uncork(); @@ -362,6 +362,7 @@ export class Logger { }); }); + this.#defaultLogger.uncork(); for (const step of this.#steps) { this.#defaultLogger.warn( `Step "${step.name}" did not finish before command shutdown. Fix this issue by updating this command to call \`step.end();\` at the appropriate time.`, @@ -387,6 +388,7 @@ export class Logger { }); }); + this.#defaultLogger.unpipe(); destroyCurrent(); showCursor(); } @@ -399,7 +401,7 @@ export class Logger { } if (step !== this.#defaultLogger && !this.#defaultLogger.isPaused()) { - this.#defaultLogger.pause(); + this.#defaultLogger.cork(); } if (step.isPiped) { @@ -434,7 +436,7 @@ export class Logger { // step.destroy(); // await step.flush(); - this.#defaultLogger.resume(); + this.#defaultLogger.uncork(); // if (step.hasError && process.env.GITHUB_RUN_ID) { // this.error('The previous step has errors.'); @@ -450,7 +452,7 @@ export class Logger { await new Promise((resolve) => { setImmediate(() => { setImmediate(() => { - this.#defaultLogger.pause(); + this.#defaultLogger.cork(); resolve(); }); }); diff --git a/modules/logger/src/__tests__/LogStep.test.ts b/modules/logger/src/__tests__/LogStep.test.ts index 8835a5f6..f9e6bdb7 100644 --- a/modules/logger/src/__tests__/LogStep.test.ts +++ b/modules/logger/src/__tests__/LogStep.test.ts @@ -1,268 +1,160 @@ import { PassThrough } from 'node:stream'; -import pc from 'picocolors'; import { LogStep } from '../LogStep'; - -// describe('LogStep', () => { -// let runId: string | undefined; - -// beforeEach(() => { -// runId = process.env.GITHUB_RUN_ID; -// delete process.env.GITHUB_RUN_ID; -// }); - -// afterEach(() => { -// process.env.GITHUB_RUN_ID = runId; -// }); - -// test('setup', async () => { -// const onEnd = vi.fn(); -// const step = new LogStep('tacos', { onEnd, verbosity: 3, onMessage: () => {} }); - -// expect(step.name).toBe('tacos'); -// expect(step.verbosity).toBe(3); -// expect(step.active).toBe(false); -// expect(step.status).toEqual([' ┌ tacos']); -// }); - -// test('can be activated', async () => { -// const onEnd = vi.fn(); -// const step = new LogStep('tacos', { onEnd, verbosity: 3, onMessage: () => {} }); -// step.activate(); - -// expect(step.active).toBe(true); -// }); - -// test('writes group & endgroup when GITHUB_RUN_ID is set', async () => { -// process.env.GITHUB_RUN_ID = 'yes'; -// const onEnd = vi.fn(() => Promise.resolve()); -// const stream = new PassThrough(); -// const step = new LogStep('tacos', { onEnd, verbosity: 4, stream, onMessage: () => {} }); - -// let out = ''; -// stream.on('data', (chunk) => { -// out += chunk.toString(); -// }); -// step.activate(); - -// step.log('hello'); -// await step.end(); -// await step.flush(); - -// expect(out).toMatch(/^::group::tacos\n/); -// expect(out).toMatch(/::endgroup::\n$/); -// }); - -// test('when activated, flushes its logs to the stream', async () => { -// vi.restoreAllMocks(); -// const onEnd = vi.fn(() => Promise.resolve()); -// const stream = new PassThrough(); -// const step = new LogStep('tacos', { onEnd, verbosity: 3, stream, onMessage: () => {} }); - -// let out = ''; -// stream.on('data', (chunk) => { -// out += chunk.toString(); -// }); - -// step.log('hellooooo'); -// step.activate(); -// await step.end(); -// await step.flush(); - -// expect(out).toEqual( -// ` ┌ tacos -// │ LOG hellooooo -// └ ✔ 0ms -// `, -// ); -// }); - -// test.concurrent.each([ -// [0, []], -// [1, ['info', 'error']], -// [2, ['info', 'error', 'warn']], -// [3, ['info', 'error', 'warn', 'log']], -// [4, ['info', 'error', 'warn', 'log', 'debug']], -// [5, ['info', 'error', 'warn', 'log', 'debug', 'timing']], -// ] as Array<[number, Array]>)('verbosity = %d writes %j', async (verbosity, methods) => { -// const onEnd = vi.fn(() => Promise.resolve()); -// const stream = new PassThrough(); -// const step = new LogStep('tacos', { onEnd, verbosity, stream, onMessage: () => {} }); - -// const logs = { -// info: `${pc.blue(pc.bold('INFO'))} some information`, -// error: ` │ ${pc.red(pc.bold('ERR'))} an error`, -// warn: ` │ ${pc.yellow(pc.bold('WRN'))} a warning`, -// log: ` │ ${pc.cyan(pc.bold('LOG'))} a log`, -// debug: ` │ ${pc.magenta(pc.bold('DBG'))} a debug`, -// timing: ` │ ${pc.red('⏳')} foo → bar: 0ms`, -// }; - -// let out = ''; -// stream.on('data', (chunk) => { -// out += chunk.toString(); -// }); - -// step.activate(); - -// step.info('some information'); -// step.error('an error'); -// step.warn('a warning'); -// step.log('a log'); -// step.debug('a debug'); -// performance.mark('foo'); -// performance.mark('bar'); -// step.timing('foo', 'bar'); - -// await step.end(); -// await step.flush(); - -// // Some funky stuff happening here -// // @ts-ignore -// if (verbosity === 0) { -// stream.end(); -// } - -// for (const [method, str] of Object.entries(logs)) { -// // @ts-ignore -// if (!methods.includes(method)) { -// expect(out).not.toMatch(str); -// } else { -// expect(out).toMatch(str); -// } -// } -// }); - -// test.concurrent.each([ -// [ -// 'function', -// function foo(asdf: unknown) { -// return asdf; -// }, -// ` │ ${pc.cyan(pc.bold('LOG'))} function foo(asdf) {`, -// ], -// [ -// 'function with zero arguments are executed', -// function foo() { -// return 'tacos'; -// }, -// ` │ ${pc.cyan(pc.bold('LOG'))} tacos`, -// ], -// [ -// 'object', -// { foo: 'bar' }, -// ` │ ${pc.cyan(pc.bold('LOG'))} { -// │ ${pc.cyan(pc.bold('LOG'))} "foo": "bar" -// │ ${pc.cyan(pc.bold('LOG'))} }`, -// ], -// [ -// 'array', -// ['foo', true], -// ` │ ${pc.cyan(pc.bold('LOG'))} [ -// │ ${pc.cyan(pc.bold('LOG'))} "foo", -// │ ${pc.cyan(pc.bold('LOG'))} true -// │ ${pc.cyan(pc.bold('LOG'))} ]`, -// ], -// ['date', new Date('2023-03-11'), ` │ ${pc.cyan(pc.bold('LOG'))} 2023-03-11T00:00:00.000Z`], -// ])('can stringify %s', async (name, obj, exp) => { -// const onEnd = vi.fn(() => Promise.resolve()); -// const stream = new PassThrough(); -// const step = new LogStep('tacos', { onEnd, verbosity: 3, stream, onMessage: () => {} }); - -// let out = ''; -// stream.on('data', (chunk) => { -// out += chunk.toString(); -// }); - -// step.log(obj); -// step.activate(); -// await step.end(); -// await step.flush(); - -// expect(out).toMatch(exp); -// }); - -// test('can omit prefixes', async () => { -// const onEnd = vi.fn(() => Promise.resolve()); -// const stream = new PassThrough(); -// const step = new LogStep('tacos', { onEnd, verbosity: 4, stream, writePrefixes: false, onMessage: () => {} }); - -// let out = ''; -// stream.on('data', (chunk) => { -// out += chunk.toString(); -// }); - -// step.error('error'); -// step.warn('warn'); -// step.info('info'); -// step.log('log'); -// step.debug('debug'); -// step.activate(); -// await step.end(); -// await step.flush(); - -// expect(out).toEqual(` ┌ tacos -// │error -// │warn -// │info -// │log -// │debug -// └ ✘ 0ms -// `); -// }); - -// test('sets hasError/etc when messages are added', async () => { -// const onEnd = vi.fn(() => Promise.resolve()); -// const stream = new PassThrough(); -// const step = new LogStep('tacos', { onEnd, verbosity: 4, stream, onMessage: () => {} }); -// step.activate(); - -// expect(step.hasError).toBe(false); -// expect(step.hasWarning).toBe(false); -// expect(step.hasInfo).toBe(false); -// expect(step.hasLog).toBe(false); - -// step.error('foo'); -// expect(step.hasError).toBe(true); - -// step.warn('foo'); -// expect(step.hasWarning).toBe(true); - -// step.info('foo'); -// expect(step.hasInfo).toBe(true); - -// step.log('foo'); -// expect(step.hasLog).toBe(true); - -// await step.end(); -// await step.flush(); -// stream.destroy(); -// }); - -// test('calls onMessage as messages are added', async () => { -// const onMessage = vi.fn(); -// const stream = new PassThrough(); -// const step = new LogStep('tacos', { onEnd: () => Promise.resolve(), verbosity: 4, stream, onMessage }); -// step.activate(); - -// expect(onMessage).not.toHaveBeenCalled(); - -// step.error('foo'); -// expect(onMessage).toHaveBeenCalledWith('error'); - -// step.warn('foo'); -// expect(onMessage).toHaveBeenCalledWith('warn'); - -// step.info('foo'); -// expect(onMessage).toHaveBeenCalledWith('info'); - -// step.log('foo'); -// expect(onMessage).toHaveBeenCalledWith('log'); - -// step.debug('foo'); -// expect(onMessage).toHaveBeenCalledWith('debug'); - -// await step.end(); -// await step.flush(); -// stream.destroy(); -// }); -// }); +import type { Verbosity } from '../types'; + +const parser = (out: Array) => { + const stream = new PassThrough(); + stream.on('data', (chunk) => { + out.push(JSON.parse(chunk.toString())); + }); + return stream; +}; + +const waitAtick = () => + new Promise((resolve) => { + setImmediate(() => { + setImmediate(() => { + resolve(); + }); + }); + }); + +describe('LogStep', () => { + test('setup', async () => { + const step = new LogStep({ name: 'tacos', verbosity: 3 }); + + expect(step.name).toBe('tacos'); + expect(step.isPiped).toBe(false); + }); + + test('can be piped', async () => { + const out: Array = []; + const step = new LogStep({ name: 'tacos', verbosity: 3 }); + const stream = parser(out); + step.pipe(stream); + step.end(); + + await waitAtick(); + + expect(out).toEqual([ + { type: 'start', contents: 'tacos', group: 'tacos', verbosity: 3 }, + { type: 'end', contents: expect.stringContaining('ms'), group: 'tacos', hasError: false, verbosity: 3 }, + ]); + }); + + test.concurrent.each([ + [0, []], + [1, ['info', 'error']], + [2, ['info', 'error', 'warn']], + [3, ['info', 'error', 'warn', 'log']], + [4, ['info', 'error', 'warn', 'log', 'debug']], + [5, ['info', 'error', 'warn', 'log', 'debug', 'timing']], + ] as Array<[Verbosity, Array]>)('verbosity = %d writes %j', async (verbosity, methods) => { + const logs = { + info: { type: 'info', contents: 'some information', verbosity, group: 'tacos' }, + error: { type: 'error', contents: 'an error', verbosity, group: 'tacos' }, + warn: { type: 'warn', contents: 'a warning', verbosity, group: 'tacos' }, + log: { type: 'log', contents: 'a log', verbosity, group: 'tacos' }, + debug: { type: 'debug', contents: 'a debug', verbosity, group: 'tacos' }, + timing: { type: 'timing', contents: 'foo → bar: 0ms', verbosity, group: 'tacos' }, + }; + + const out: Array = []; + const stream = parser(out); + const step = new LogStep({ name: 'tacos', verbosity }); + step.pipe(stream); + + step.info('some information'); + step.error('an error'); + step.warn('a warning'); + step.log('a log'); + step.debug('a debug'); + performance.mark('foo'); + performance.mark('bar'); + step.timing('foo', 'bar'); + + step.end(); + + await waitAtick(); + + for (const [method, str] of Object.entries(logs)) { + // @ts-ignore + if (!methods.includes(method)) { + expect(out).not.toEqual(expect.arrayContaining(['asdf'])); + } else { + expect(out).toEqual(expect.arrayContaining([str])); + } + } + }); + + test.concurrent.each([ + [ + 'function', + function foo(asdf: unknown) { + return asdf; + }, + [{ type: 'log', contents: expect.stringContaining('function foo(asdf) {'), verbosity: 3, group: 'tacos' }], + ], + [ + 'function with zero arguments are executed', + function foo() { + return 'tacos'; + }, + [{ type: 'log', contents: 'tacos', verbosity: 3, group: 'tacos' }], + ], + [ + 'object', + { foo: 'bar' }, + [{ type: 'log', contents: JSON.stringify({ foo: 'bar' }, null, 2), verbosity: 3, group: 'tacos' }], + ], + [ + 'array', + ['foo', true], + [{ type: 'log', contents: JSON.stringify(['foo', true], null, 2), verbosity: 3, group: 'tacos' }], + ], + [ + 'date', + new Date('2023-03-11'), + [{ type: 'log', contents: '2023-03-11T00:00:00.000Z', verbosity: 3, group: 'tacos' }], + ], + ])('can stringify %s', async (name, obj, exp) => { + const out: Array = []; + const stream = parser(out); + const step = new LogStep({ name: 'tacos', verbosity: 3 }); + step.pipe(stream); + + step.log(obj); + step.end(); + + await waitAtick(); + + expect(out).toEqual(expect.arrayContaining(exp)); + }); + + test('sets hasError/etc when messages are added', async () => { + const out: Array = []; + const stream = parser(out); + const step = new LogStep({ name: 'tacos', verbosity: 4 }); + step.pipe(stream); + + expect(step.hasError).toBe(false); + expect(step.hasWarning).toBe(false); + expect(step.hasInfo).toBe(false); + expect(step.hasLog).toBe(false); + + step.error('foo'); + expect(step.hasError).toBe(true); + + step.warn('foo'); + expect(step.hasWarning).toBe(true); + + step.info('foo'); + expect(step.hasInfo).toBe(true); + + step.log('foo'); + expect(step.hasLog).toBe(true); + + step.end(); + stream.destroy(); + }); +}); diff --git a/modules/logger/src/__tests__/Logger.test.ts b/modules/logger/src/__tests__/Logger.test.ts index 14876a83..a7b5a31f 100644 --- a/modules/logger/src/__tests__/Logger.test.ts +++ b/modules/logger/src/__tests__/Logger.test.ts @@ -12,6 +12,7 @@ async function runPendingImmediates() { }); } + describe('Logger', () => { let runId: string | undefined; @@ -74,9 +75,9 @@ describe('Logger', () => { }, ); - test.only('logs "completed" message', async () => { - const stream = new PassThrough(); + test('logs "completed" message', async () => { let out = ''; + const stream = new PassThrough(); stream.on('data', (chunk) => { out += chunk.toString(); }); @@ -87,22 +88,16 @@ describe('Logger', () => { step.end(); await logger.end(); - await new Promise((resolve) => { - setTimeout(() => { - setImmediate(() => { - resolve(); - }); - }, 100); - }); - expect(out).toEqual('foo'); + await runPendingImmediates(); expect(out).toMatch(`${pc.dim(pc.bold('■'))} ${pc.green('✔')} Completed`); }); - test('logs "completed with errors" message', async () => { + test.only('logs "completed with errors" message', async () => { const stream = new PassThrough(); let out = ''; stream.on('data', (chunk) => { + console.log('c', chunk.toString()); out += chunk.toString(); }); @@ -110,9 +105,12 @@ describe('Logger', () => { const step = logger.createStep('tacos'); step.error('foo'); - await step.end(); + step.end(); await logger.end(); + console.log('====='); + await runPendingImmediates(); + expect(out).toEqual('foo'); expect(out).toMatch(`${pc.dim(pc.bold('■'))} ${pc.red('✘')} Completed with errors`); }); diff --git a/modules/logger/src/transforms/LogStepToString.ts b/modules/logger/src/transforms/LogStepToString.ts index 64b15d6c..197d8a7b 100644 --- a/modules/logger/src/transforms/LogStepToString.ts +++ b/modules/logger/src/transforms/LogStepToString.ts @@ -16,6 +16,7 @@ export class LogStepToString extends Transform { ) { try { const data = JSON.parse(chunk.toString()) as LoggedBuffer; + // console.log(data); if (typeMinVerbosity[data.type] <= data.verbosity) { this.push(ensureNewline(`${this.#prefix(data.type, data.group, stringify(data.contents), data.hasError)}`)); } From 3dd1466c230d2841acee8b9f1061980042f96719 Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Tue, 10 Dec 2024 19:47:37 -0800 Subject: [PATCH 13/17] refactor: use log streams in object mode --- commands/build.ts | 2 +- docs/src/content/docs/api/index.md | 52 ++++++++---- modules/logger/src/LogStep.ts | 62 ++++++-------- modules/logger/src/Logger.ts | 83 ++++++++++--------- modules/logger/src/__tests__/Logger.test.ts | 27 +++--- modules/logger/src/index.ts | 6 +- .../logger/src/transforms/LogStepToString.ts | 17 ++-- modules/onerepo/src/core/install/index.ts | 7 +- modules/onerepo/src/core/tasks/tasks.ts | 2 +- modules/onerepo/src/setup/setup.ts | 13 ++- plugins/performance-writer/src/index.ts | 4 +- 11 files changed, 140 insertions(+), 135 deletions(-) diff --git a/commands/build.ts b/commands/build.ts index 62ca93af..a9a3801d 100644 --- a/commands/build.ts +++ b/commands/build.ts @@ -45,7 +45,7 @@ export const handler: Handler = async function handler(argv, { getWorkspac for (const workspace of workspaces) { if (workspace.private) { - // buildableStep.warn(`Not building \`${workspace.name}\` because it is private`); + buildableStep.info(`Skipping "${workspace.name}" because it is private`); continue; } diff --git a/docs/src/content/docs/api/index.md b/docs/src/content/docs/api/index.md index 7ed12c25..1b087b12 100644 --- a/docs/src/content/docs/api/index.md +++ b/docs/src/content/docs/api/index.md @@ -4,7 +4,7 @@ description: Full API documentation for oneRepo. --- - + ## Variables @@ -1953,9 +1953,9 @@ await step.en(); **Parameters:** -| Parameter | Type | -| --------- | --------------------- | -| `step` | [`LogStep`](#logstep) | +| Parameter | Type | +| --------- | ----------------------------------- | +| `step` | [`LogStep`](#logstep) \| `Writable` | **Returns:** ```ts { @@ -2181,12 +2181,13 @@ await step.end(); **Parameters:** -| Parameter | Type | Description | -| --------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | `string` | The name to be written and wrapped around any output logged to this new step. | -| `opts`? | \{ `description`: `string`; `writePrefixes`: `boolean`; \} | - | -| `opts.description`? | `string` | Optionally include extra information for performance tracing on this step. This description will be passed through to the [`performanceMark.detail`](https://nodejs.org/docs/latest-v20.x/api/perf_hooks.html#performancemarkdetail) recorded internally for this step. Use a [Performance Writer plugin](https://onerepo.tools/plugins/performance-writer/) to read and work with this detail. | -| `opts.writePrefixes`? | `boolean` | **Deprecated** This option no longer does anything and will be removed in v2.0.0 | +| Parameter | Type | Description | +| --------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | `string` | The name to be written and wrapped around any output logged to this new step. | +| `opts`? | \{ `description`: `string`; `verbosity`: [`Verbosity`](#verbosity-2); `writePrefixes`: `boolean`; \} | - | +| `opts.description`? | `string` | Optionally include extra information for performance tracing on this step. This description will be passed through to the [`performanceMark.detail`](https://nodejs.org/docs/latest-v20.x/api/perf_hooks.html#performancemarkdetail) recorded internally for this step. Use a [Performance Writer plugin](https://onerepo.tools/plugins/performance-writer/) to read and work with this detail. | +| `opts.verbosity`? | [`Verbosity`](#verbosity-2) | Override the default logger verbosity. Any changes while this step is running to the default logger will result in this step’s verbosity changing as well. | +| `opts.writePrefixes`? | `boolean` | **Deprecated** This option no longer does anything and will be removed in v2.0.0 | **Returns:** [`LogStep`](#logstep) **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) @@ -2216,11 +2217,20 @@ logger.unpause(); unpause(): void ``` -Unpause the logger and resume writing buffered logs to the output stream. See [\`logger.pause()\`](#pause) for more information. +Unpause the logger and uncork writing buffered logs to the output stream. See [\`logger.pause()\`](#pause) for more information. **Returns:** `void` **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) +##### waitForClear() + +```ts +waitForClear(): Promise +``` + +**Returns:** `Promise`\<`boolean`\> +**Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) + #### Logging ##### debug() @@ -2518,7 +2528,7 @@ set verbosity(verbosity): void ##### end() ```ts -end(): this +end(callback?): this ``` Signal the end of this step. After this method is called, it will no longer accept any more logs of any variety and will be removed from the parent Logger's queue. @@ -2531,6 +2541,12 @@ const myStep = logger.createStep('My step'); myStep.end(); ``` +**Parameters:** + +| Parameter | Type | +| ----------- | ------------ | +| `callback`? | () => `void` | + **Returns:** `this` ###### Overrides @@ -2727,16 +2743,16 @@ write( Write directly to the step's stream, bypassing any formatting and verbosity filtering. :::caution[Advanced] -Since [LogStep](#logstep) implements a [Node.js duplex stream](https://nodejs.org/docs/latest-v20.x/api/stream.html#class-streamduplex), it is possible to use internal `write`, `read`, `pipe`, and all other available methods, but may not be fully recommended. +Since [LogStep](#logstep) implements a [Node.js duplex stream](https://nodejs.org/docs/latest-v20.x/api/stream.html#class-streamduplex) in `objectMode`, it is possible to use internal `write`, `read`, `pipe`, and all other available methods, but may not be fully recommended. ::: **Parameters:** -| Parameter | Type | -| ----------- | ------------------- | -| `chunk` | `any` | -| `encoding`? | `BufferEncoding` | -| `cb`? | (`error`) => `void` | +| Parameter | Type | +| ----------- | -------------------------- | +| `chunk` | `string` \| `LoggedBuffer` | +| `encoding`? | `BufferEncoding` | +| `cb`? | (`error`) => `void` | **Returns:** `boolean` diff --git a/modules/logger/src/LogStep.ts b/modules/logger/src/LogStep.ts index 7d0b6699..801233da 100644 --- a/modules/logger/src/LogStep.ts +++ b/modules/logger/src/LogStep.ts @@ -59,7 +59,7 @@ export class LogStep extends Duplex { */ constructor(options: LogStepOptions) { const { description, name, verbosity } = options; - super({ decodeStrings: false }); + super({ decodeStrings: false, objectMode: true }); this.#verbosity = verbosity; this.#startMark = name || `${performance.now()}`; @@ -75,14 +75,13 @@ export class LogStep extends Duplex { * Write directly to the step's stream, bypassing any formatting and verbosity filtering. * * :::caution[Advanced] - * Since {@link LogStep} implements a [Node.js duplex stream](https://nodejs.org/docs/latest-v20.x/api/stream.html#class-streamduplex), it is possible to use internal `write`, `read`, `pipe`, and all other available methods, but may not be fully recommended. + * Since {@link LogStep} implements a [Node.js duplex stream](https://nodejs.org/docs/latest-v20.x/api/stream.html#class-streamduplex) in `objectMode`, it is possible to use internal `write`, `read`, `pipe`, and all other available methods, but may not be fully recommended. * ::: * * @group Logging */ write( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - chunk: any, + chunk: LoggedBuffer | string, encoding?: BufferEncoding, cb?: (error: Error | null | undefined) => void, ): boolean; @@ -90,11 +89,7 @@ export class LogStep extends Duplex { /** * @internal */ - write( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - chunk: any, - cb?: (error: Error | null | undefined) => void, - ): boolean; + write(chunk: LoggedBuffer | string, cb?: (error: Error | null | undefined) => void): boolean; write( // @ts-expect-error @@ -131,16 +126,12 @@ export class LogStep extends Duplex { } #write(type: LineType, contents: unknown) { - this.write( - Buffer.from( - JSON.stringify({ - type, - contents: stringify(contents), - group: this.name, - verbosity: this.#verbosity, - } satisfies LoggedBuffer), - ), - ); + this.write({ + type, + contents: stringify(contents), + group: this.name, + verbosity: this.#verbosity, + } satisfies LoggedBuffer); } set verbosity(verbosity: Verbosity) { @@ -178,8 +169,8 @@ export class LogStep extends Duplex { /** * @internal */ - set hasInfo(hasWarning: boolean) { - this.#hasInfo = this.#hasInfo || hasWarning; + set hasInfo(hasInfo: boolean) { + this.#hasInfo = this.#hasInfo || hasInfo; } /** @@ -224,7 +215,7 @@ export class LogStep extends Duplex { * @group Logging */ error(contents: unknown) { - this.#hasError = true; + this.hasError = true; this.#write('error', contents); } @@ -249,7 +240,7 @@ export class LogStep extends Duplex { * @group Logging */ warn(contents: unknown) { - this.#hasWarning = true; + this.hasWarning = true; this.#write('warn', contents); } @@ -273,7 +264,7 @@ export class LogStep extends Duplex { * @group Logging */ info(contents: unknown) { - this.#hasInfo = true; + this.hasInfo = true; this.#write('info', contents); } @@ -297,7 +288,7 @@ export class LogStep extends Duplex { * @group Logging */ log(contents: unknown) { - this.#hasLog = true; + this.hasLog = true; this.#write('log', contents); } @@ -364,7 +355,7 @@ export class LogStep extends Duplex { * myStep.end(); * ``` */ - end() { + end(callback?: () => void) { // Makes calling `.end()` multiple times safe. // TODO: make this unnecessary if (this.writableEnded) { @@ -379,18 +370,17 @@ export class LogStep extends Duplex { !startMark || process.env.NODE_ENV === 'test' ? 0 : Math.round(endMark.startTime - startMark.startTime); const contents = this.name ? pc.dim(`${duration}ms`) - : `Completed${this.#hasError ? ' with errors' : ''} ${pc.dim(`${duration}ms`)}`; + : `Completed${this.hasError ? ' with errors' : ''} ${pc.dim(`${duration}ms`)}`; return super.end( - Buffer.from( - JSON.stringify({ - type: 'end', - contents: stringify(contents), - group: this.name, - hasError: this.#hasError, - verbosity: this.#verbosity, - } satisfies LoggedBuffer), - ), + { + type: 'end', + contents: stringify(contents), + group: this.name, + hasError: this.#hasError, + verbosity: this.#verbosity, + } satisfies LoggedBuffer, + callback, ); } } diff --git a/modules/logger/src/Logger.ts b/modules/logger/src/Logger.ts index 9d4421e2..10f2c160 100644 --- a/modules/logger/src/Logger.ts +++ b/modules/logger/src/Logger.ts @@ -1,6 +1,5 @@ import type { Writable } from 'node:stream'; import { destroyCurrent, setCurrent } from './global'; -import type { LogStepOptions } from './LogStep'; import { LogStep } from './LogStep'; import { LogStepToString } from './transforms/LogStepToString'; import { LogProgress } from './transforms/LogProgress'; @@ -83,7 +82,7 @@ export class Logger { if (this.#defaultLogger) { this.#defaultLogger.verbosity = this.#verbosity; - this.#activate(this.#defaultLogger); + // this.#activate(this.#defaultLogger); } this.#steps.forEach((step) => (step.verbosity = this.#verbosity)); @@ -179,13 +178,17 @@ export class Logger { * Use a [Performance Writer plugin](https://onerepo.tools/plugins/performance-writer/) to read and work with this detail. */ description?: string; + /** + * Override the default logger verbosity. Any changes while this step is running to the default logger will result in this step’s verbosity changing as well. + */ + verbosity?: Verbosity; /** * @deprecated This option no longer does anything and will be removed in v2.0.0 */ writePrefixes?: boolean; } = {}, ) { - const step = new LogStep({ name, verbosity: this.#verbosity, description: opts.description }); + const step = new LogStep({ name, verbosity: opts.verbosity ?? this.#verbosity, description: opts.description }); this.#steps.push(step); step.on('end', () => this.#onEnd(step)); @@ -348,47 +351,44 @@ export class Logger { this.#defaultLogger.timing(start, end); } + async waitForClear() { + return await new Promise((resolve) => { + setImmediate(() => { + resolve(this.#steps.length === 0); + }); + }); + } + /** * @internal */ async end() { this.unpause(); - await new Promise((resolve) => { - setImmediate(() => { - setImmediate(() => { - resolve(); - }); - }); - }); - - this.#defaultLogger.uncork(); - for (const step of this.#steps) { - this.#defaultLogger.warn( - `Step "${step.name}" did not finish before command shutdown. Fix this issue by updating this command to call \`step.end();\` at the appropriate time.`, - ); - await this.#onEnd(step); + const now = Date.now(); + while ((await this.waitForClear()) === false) { + if (Date.now() - now > 100) { + const openStep = this.#steps[0]; + if (openStep) { + openStep.error( + 'Did not complete before command shutdown. Fix this issue by updating this command to call `step.end();` at the appropriate time.', + ); + openStep.end(); + } + } + continue; } + this.#activate(this.#defaultLogger); + this.#defaultLogger.uncork(); await new Promise((resolve) => { - setImmediate(() => { - setImmediate(() => { - resolve(); - }); - }); - }); - - this.#defaultLogger.end(); - - await new Promise((resolve) => { - setImmediate(() => { - setImmediate(() => { - resolve(); - }); + this.#defaultLogger.end(() => { + resolve(); }); }); this.#defaultLogger.unpipe(); + destroyCurrent(); showCursor(); } @@ -411,9 +411,12 @@ export class Logger { hideCursor(); if (!step.name || !(this.#stream as typeof process.stderr).isTTY) { - step.pipe(new LogStepToString()).pipe(this.#stream); + step.pipe(new LogStepToString()).pipe(this.#stream as Writable); } else { - step.pipe(new LogStepToString()).pipe(new LogProgress()).pipe(this.#stream); + step + .pipe(new LogStepToString()) + .pipe(new LogProgress()) + .pipe(this.#stream as Writable); } step.isPiped = true; } @@ -438,9 +441,9 @@ export class Logger { this.#defaultLogger.uncork(); - // if (step.hasError && process.env.GITHUB_RUN_ID) { - // this.error('The previous step has errors.'); - // } + if (step.hasError && process.env.GITHUB_RUN_ID) { + this.error('The previous step has errors.'); + } // Remove this step this.#steps.splice(index, 1); @@ -462,9 +465,9 @@ export class Logger { }; #setState = (step: LogStep) => { - this.#defaultLogger.hasError = this.#defaultLogger.hasError || step.hasError; - this.#defaultLogger.hasWarning = this.#defaultLogger.hasWarning || step.hasWarning; - this.#defaultLogger.hasInfo = this.#defaultLogger.hasInfo || step.hasInfo; - this.#defaultLogger.hasLog = this.#defaultLogger.hasLog || step.hasLog; + this.#defaultLogger.hasError = step.hasError || this.#defaultLogger.hasError; + this.#defaultLogger.hasWarning = step.hasWarning || this.#defaultLogger.hasWarning; + this.#defaultLogger.hasInfo = step.hasInfo || this.#defaultLogger.hasInfo; + this.#defaultLogger.hasLog = step.hasLog || this.#defaultLogger.hasLog; }; } diff --git a/modules/logger/src/__tests__/Logger.test.ts b/modules/logger/src/__tests__/Logger.test.ts index a7b5a31f..fc6fc07b 100644 --- a/modules/logger/src/__tests__/Logger.test.ts +++ b/modules/logger/src/__tests__/Logger.test.ts @@ -12,7 +12,6 @@ async function runPendingImmediates() { }); } - describe('Logger', () => { let runId: string | undefined; @@ -75,7 +74,7 @@ describe('Logger', () => { }, ); - test('logs "completed" message', async () => { + test.only('logs "completed" message', async () => { let out = ''; const stream = new PassThrough(); stream.on('data', (chunk) => { @@ -90,14 +89,13 @@ describe('Logger', () => { await logger.end(); await runPendingImmediates(); - expect(out).toMatch(`${pc.dim(pc.bold('■'))} ${pc.green('✔')} Completed`); + expect(out).toEqual(`${pc.dim(pc.bold('■'))} ${pc.green('✔')} ${pc.dim('0ms')}`); }); - test.only('logs "completed with errors" message', async () => { + test('logs "completed with errors" message', async () => { const stream = new PassThrough(); let out = ''; stream.on('data', (chunk) => { - console.log('c', chunk.toString()); out += chunk.toString(); }); @@ -108,11 +106,9 @@ describe('Logger', () => { step.end(); await logger.end(); - console.log('====='); await runPendingImmediates(); - expect(out).toEqual('foo'); - expect(out).toMatch(`${pc.dim(pc.bold('■'))} ${pc.red('✘')} Completed with errors`); + expect(out).toEqual(`${pc.dim(pc.bold('■'))} ${pc.red('✘')} Completed with errors`); }); test('writes logs if verbosity increased after construction', async () => { @@ -128,7 +124,7 @@ describe('Logger', () => { logger.warn('this is a warning'); await runPendingImmediates(); const step = logger.createStep('tacos'); - await step.end(); + step.end(); await logger.end(); @@ -157,7 +153,7 @@ describe('Logger', () => { expect(out).not.toMatch('::endgroup::'); }); - test.concurrent.each([ + test.each([ ['error', 'hasError'], ['warn', 'hasWarning'], ['info', 'hasInfo'], @@ -169,18 +165,21 @@ describe('Logger', () => { const logger = new Logger({ verbosity: 2, stream }); const step = logger.createStep('tacos'); - await step.end(); + step.end(); expect(logger[getter]).toBe(false); - const step2 = logger.createStep('burritos'); + const step2 = logger.createStep(`${method.toString()} ${getter}`); // @ts-ignore step2[method]('yum'); - await step2.end(); + step2.end(); + await logger.end(); + await runPendingImmediates(); + // @ts-ignore + expect(step2[getter]).toBe(true); expect(logger[getter]).toBe(true); - await logger.end(); stream.destroy(); }, ); diff --git a/modules/logger/src/index.ts b/modules/logger/src/index.ts index e36b2eff..31274797 100644 --- a/modules/logger/src/index.ts +++ b/modules/logger/src/index.ts @@ -1,7 +1,7 @@ +import type { Writable } from 'node:stream'; import { Transform } from 'node:stream'; import restoreCursorDefault from 'restore-cursor'; import { Logger } from './Logger'; -// import { LogStep } from './LogStep'; import { destroyCurrent, getCurrent, setCurrent } from './global'; import type { LogStep } from './LogStep'; import { prefix } from './transforms/LogStepToString'; @@ -95,12 +95,12 @@ export async function stepWrapper( * @alpha * @group Logger */ -export function bufferSubLogger(step: LogStep): { logger: Logger; end: () => Promise } { +export function bufferSubLogger(step: Writable | LogStep): { logger: Logger; end: () => Promise } { const logger = getLogger(); const stream = new Buffered(); const subLogger = new Logger({ verbosity: logger.verbosity, stream, captureAll: true }); - stream.pipe(step); + stream.pipe(step as Writable); return { logger: subLogger, diff --git a/modules/logger/src/transforms/LogStepToString.ts b/modules/logger/src/transforms/LogStepToString.ts index 197d8a7b..c1ead42f 100644 --- a/modules/logger/src/transforms/LogStepToString.ts +++ b/modules/logger/src/transforms/LogStepToString.ts @@ -5,23 +5,26 @@ import { ensureNewline, stringify } from '../utils/string'; export class LogStepToString extends Transform { constructor() { - super({ decodeStrings: false }); + super({ decodeStrings: false, objectMode: true }); } _transform( - chunk: Buffer, + chunk: LoggedBuffer | string, // eslint-disable-next-line @typescript-eslint/no-unused-vars encoding = 'utf8', callback: () => void, ) { try { - const data = JSON.parse(chunk.toString()) as LoggedBuffer; - // console.log(data); - if (typeMinVerbosity[data.type] <= data.verbosity) { - this.push(ensureNewline(`${this.#prefix(data.type, data.group, stringify(data.contents), data.hasError)}`)); + if (typeof chunk === 'string') { + this.push(chunk); + } else { + const data = chunk as LoggedBuffer; + if (typeMinVerbosity[data.type] <= data.verbosity) { + this.push(ensureNewline(`${this.#prefix(data.type, data.group, stringify(data.contents), data.hasError)}`)); + } } } catch (e) { - this.push(chunk); + this.push(JSON.stringify(chunk)); } callback(); } diff --git a/modules/onerepo/src/core/install/index.ts b/modules/onerepo/src/core/install/index.ts index afd850d4..09f943b5 100644 --- a/modules/onerepo/src/core/install/index.ts +++ b/modules/onerepo/src/core/install/index.ts @@ -31,9 +31,7 @@ export const install: Plugin = function install() { } const logger = getLogger(); - const oVerbosity = logger.verbosity; - logger.verbosity = 2; - const step = logger.createStep('Version mismatch detected!'); + const step = logger.createStep('Version mismatch detected!', { verbosity: 2 }); const bar = '⎯'.repeat(Math.min(process.stderr.columns, 70)); if ( @@ -61,8 +59,7 @@ ${bar} ${bar}`); } - await step.end(); - logger.verbosity = oVerbosity; + step.end(); }, }; }; diff --git a/modules/onerepo/src/core/tasks/tasks.ts b/modules/onerepo/src/core/tasks/tasks.ts index 6845c541..4c559a34 100644 --- a/modules/onerepo/src/core/tasks/tasks.ts +++ b/modules/onerepo/src/core/tasks/tasks.ts @@ -298,7 +298,7 @@ function singleTaskToSpec( await yargs.parse(); await subLogger.end(); - await step.end(); + step.end(); return ['', '']; }; } diff --git a/modules/onerepo/src/setup/setup.ts b/modules/onerepo/src/setup/setup.ts index e67395a9..16d01c7e 100644 --- a/modules/onerepo/src/setup/setup.ts +++ b/modules/onerepo/src/setup/setup.ts @@ -9,8 +9,8 @@ import { globSync } from 'glob'; import { commandDirOptions, setupYargs } from '@onerepo/yargs'; import type { Graph } from '@onerepo/graph'; import { getGraph } from '@onerepo/graph'; -import type { Verbosity } from '@onerepo/logger'; -import { Logger, getLogger } from '@onerepo/logger'; +import type { Verbosity, Logger } from '@onerepo/logger'; +import { getLogger } from '@onerepo/logger'; import type { RequireDirectoryOptions, Argv as Yargv } from 'yargs'; import type { Argv, DefaultArgv, Yargs } from '@onerepo/yargs'; import { flushUpdateIndex } from '@onerepo/git'; @@ -113,7 +113,7 @@ export async function setup({ process.env.ONEREPO_HEAD_BRANCH = head; process.env.ONEREPO_DRY_RUN = 'false'; - const graph = await (inputGraph || getGraph(process.env.ONEREPO_ROOT)); + const graph = inputGraph || getGraph(process.env.ONEREPO_ROOT); const yargs = setupYargs(yargsInstance.scriptName('one'), { graph, logger }); yargs @@ -198,16 +198,13 @@ export async function setup({ const logger = getLogger(); // Enforce the initial verbosity, in case it was modified logger.verbosity = argv.verbosity as Verbosity; + + await shutdown(argv); await logger.end(); if (logger.hasError) { process.exitCode = 1; } - - // Register a new logger on the top of the stack to silence output so that shutdown handlers to not write any output - const silencedLogger = new Logger({ verbosity: 0 }); - await shutdown(argv); - await silencedLogger.end(); }, }; } diff --git a/plugins/performance-writer/src/index.ts b/plugins/performance-writer/src/index.ts index b820875a..f95a3641 100644 --- a/plugins/performance-writer/src/index.ts +++ b/plugins/performance-writer/src/index.ts @@ -97,7 +97,7 @@ export function performanceWriter(opts: Options = {}): PluginObject { shutdown: async () => { const logger = getLogger(); - const step = logger.createStep('Report metrics'); + const step = logger.createStep('Report metrics', { verbosity: 0 }); observer.disconnect(); const measures = performance.getEntriesByType('measure'); @@ -111,7 +111,7 @@ export function performanceWriter(opts: Options = {}): PluginObject { } await file.write(outFile, JSON.stringify(measures), { step }); - await step.end(); + step.end(); }, }; } From 85b2e71630c14d73e985f2dd2759c69fc8e99248 Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Tue, 17 Dec 2024 17:58:33 -0800 Subject: [PATCH 14/17] fix(logger): buffer sub logger ending immediately --- commands/build.ts | 4 +- docs/src/content/docs/api/index.md | 45 +++++++------- modules/builders/src/getters.ts | 6 +- modules/github-action/dist/get-tasks.cjs | 60 +++++++++---------- modules/github-action/dist/run-task.cjs | 60 +++++++++---------- modules/logger/src/LogStep.ts | 14 ++--- modules/logger/src/Logger.ts | 26 ++++---- modules/logger/src/global.ts | 10 +--- modules/logger/src/index.ts | 37 +++++++++--- .../logger/src/transforms/LogStepToString.ts | 4 +- modules/onerepo/src/core/tasks/tasks.ts | 11 ++-- modules/subprocess/src/index.ts | 24 ++++---- plugins/vitest/src/commands/vitest.ts | 2 +- 13 files changed, 153 insertions(+), 150 deletions(-) diff --git a/commands/build.ts b/commands/build.ts index a9a3801d..22785e45 100644 --- a/commands/build.ts +++ b/commands/build.ts @@ -154,11 +154,11 @@ export const handler: Handler = async function handler(argv, { getWorkspac } } - await buildableStep.end(); + buildableStep.end(); const removeStep = logger.createStep('Clean previous build directories'); await Promise.all(removals.map((dir) => file.remove(dir, { step: removeStep }))); - await removeStep.end(); + removeStep.end(); await batch([...buildProcs, ...typesProcs]); await Promise.all(postCopy.map((fn) => fn())); diff --git a/docs/src/content/docs/api/index.md b/docs/src/content/docs/api/index.md index 1b087b12..b3f10f43 100644 --- a/docs/src/content/docs/api/index.md +++ b/docs/src/content/docs/api/index.md @@ -4,7 +4,7 @@ description: Full API documentation for oneRepo. --- - + ## Variables @@ -1953,9 +1953,9 @@ await step.en(); **Parameters:** -| Parameter | Type | -| --------- | ----------------------------------- | -| `step` | [`LogStep`](#logstep) \| `Writable` | +| Parameter | Type | +| --------- | --------------------- | +| `step` | [`LogStep`](#logstep) | **Returns:** ```ts { @@ -2056,6 +2056,12 @@ If the current terminal is a TTY, output will be buffered and asynchronous steps See [\`HandlerExtra\`](#handlerextra) for access the the global Logger instance. +#### Properties + +| Property | Type | Defined in | +| -------- | -------- | --------------------------------------------------------------------------------------------------------------- | +| `id` | `string` | [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) | + #### Accessors ##### captureAll @@ -2133,7 +2139,7 @@ get verbosity(): Verbosity Get the logger's verbosity level -**Returns:** [`Verbosity`](#verbosity-2) +**Returns:** [`Verbosity`](#verbosity-1) ###### Set Signature @@ -2147,7 +2153,7 @@ Applies the new verbosity to the main logger and any future steps. | Parameter | Type | | --------- | --------------------------- | -| `value` | [`Verbosity`](#verbosity-2) | +| `value` | [`Verbosity`](#verbosity-1) | **Returns:** `void` **Defined in:** [modules/logger/src/Logger.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/Logger.ts) @@ -2184,9 +2190,9 @@ await step.end(); | Parameter | Type | Description | | --------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | `string` | The name to be written and wrapped around any output logged to this new step. | -| `opts`? | \{ `description`: `string`; `verbosity`: [`Verbosity`](#verbosity-2); `writePrefixes`: `boolean`; \} | - | +| `opts`? | \{ `description`: `string`; `verbosity`: [`Verbosity`](#verbosity-1); `writePrefixes`: `boolean`; \} | - | | `opts.description`? | `string` | Optionally include extra information for performance tracing on this step. This description will be passed through to the [`performanceMark.detail`](https://nodejs.org/docs/latest-v20.x/api/perf_hooks.html#performancemarkdetail) recorded internally for this step. Use a [Performance Writer plugin](https://onerepo.tools/plugins/performance-writer/) to read and work with this detail. | -| `opts.verbosity`? | [`Verbosity`](#verbosity-2) | Override the default logger verbosity. Any changes while this step is running to the default logger will result in this step’s verbosity changing as well. | +| `opts.verbosity`? | [`Verbosity`](#verbosity-1) | Override the default logger verbosity. Any changes while this step is running to the default logger will result in this step’s verbosity changing as well. | | `opts.writePrefixes`? | `boolean` | **Deprecated** This option no longer does anything and will be removed in v2.0.0 | **Returns:** [`LogStep`](#logstep) @@ -2452,6 +2458,12 @@ myStep.end(); - `Duplex` +#### Properties + +| Property | Type | Defined in | +| ----------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `verbosity` | [`Verbosity`](#verbosity-1) | [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) | + #### Accessors ##### hasError @@ -2506,23 +2518,6 @@ Whether this step has logged a warning message. **Returns:** `boolean` **Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) -##### verbosity - -###### Set Signature - -```ts -set verbosity(verbosity): void -``` - -**Parameters:** - -| Parameter | Type | -| ----------- | --------------------------- | -| `verbosity` | [`Verbosity`](#verbosity-2) | - -**Returns:** `void` -**Defined in:** [modules/logger/src/LogStep.ts](https://github.com/paularmstrong/onerepo/blob/main/modules/logger/src/LogStep.ts) - #### Methods ##### end() diff --git a/modules/builders/src/getters.ts b/modules/builders/src/getters.ts index 0c5c1b84..a3480525 100644 --- a/modules/builders/src/getters.ts +++ b/modules/builders/src/getters.ts @@ -91,7 +91,7 @@ export function getAffected(graph: Graph, { from, ignore, staged, step, through return graph.workspaces; } - return await graph.affected(Array.from(workspaces)); + return graph.affected(Array.from(workspaces)); }); } @@ -144,7 +144,7 @@ export async function getWorkspaces( } else { const names = workspaces.map((ws) => ws.name); step.log(() => `\`affected\` requested from • ${names.join('\n • ')}`); - workspaces = await graph.affected(names); + workspaces = graph.affected(names); } } @@ -237,7 +237,7 @@ export async function getFilepaths( } } else { step.log('`affected` requested from Workspaces'); - const affected = await graph.affected(argv.workspaces!); + const affected = graph.affected(argv.workspaces!); paths.push(...affected.map((ws) => ws.location)); } } diff --git a/modules/github-action/dist/get-tasks.cjs b/modules/github-action/dist/get-tasks.cjs index 397d64f2..25482392 100644 --- a/modules/github-action/dist/get-tasks.cjs +++ b/modules/github-action/dist/get-tasks.cjs @@ -152,7 +152,7 @@ var require_command = __commonJS({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/rng.js +// node_modules/uuid/dist/esm-node/rng.js function rng() { if (poolPtr > rnds8Pool.length - 16) { import_crypto.default.randomFillSync(rnds8Pool); @@ -162,34 +162,34 @@ function rng() { } var import_crypto, rnds8Pool, poolPtr; var init_rng = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/rng.js"() { + "node_modules/uuid/dist/esm-node/rng.js"() { import_crypto = __toESM(require("crypto")); rnds8Pool = new Uint8Array(256); poolPtr = rnds8Pool.length; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/regex.js +// node_modules/uuid/dist/esm-node/regex.js var regex_default; var init_regex = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/regex.js"() { + "node_modules/uuid/dist/esm-node/regex.js"() { regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/validate.js +// node_modules/uuid/dist/esm-node/validate.js function validate(uuid) { return typeof uuid === "string" && regex_default.test(uuid); } var validate_default; var init_validate = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/validate.js"() { + "node_modules/uuid/dist/esm-node/validate.js"() { init_regex(); validate_default = validate; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/stringify.js +// node_modules/uuid/dist/esm-node/stringify.js function stringify(arr, offset = 0) { const uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); if (!validate_default(uuid)) { @@ -199,7 +199,7 @@ function stringify(arr, offset = 0) { } var byteToHex, stringify_default; var init_stringify = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/stringify.js"() { + "node_modules/uuid/dist/esm-node/stringify.js"() { init_validate(); byteToHex = []; for (let i = 0; i < 256; ++i) { @@ -209,7 +209,7 @@ var init_stringify = __esm({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/v1.js +// node_modules/uuid/dist/esm-node/v1.js function v1(options, buf, offset) { let i = buf && offset || 0; const b = buf || new Array(16); @@ -260,7 +260,7 @@ function v1(options, buf, offset) { } var _nodeId, _clockseq, _lastMSecs, _lastNSecs, v1_default; var init_v1 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/v1.js"() { + "node_modules/uuid/dist/esm-node/v1.js"() { init_rng(); init_stringify(); _lastMSecs = 0; @@ -269,7 +269,7 @@ var init_v1 = __esm({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/parse.js +// node_modules/uuid/dist/esm-node/parse.js function parse(uuid) { if (!validate_default(uuid)) { throw TypeError("Invalid UUID"); @@ -296,13 +296,13 @@ function parse(uuid) { } var parse_default; var init_parse = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/parse.js"() { + "node_modules/uuid/dist/esm-node/parse.js"() { init_validate(); parse_default = parse; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/v35.js +// node_modules/uuid/dist/esm-node/v35.js function stringToBytes(str) { str = unescape(encodeURIComponent(str)); const bytes = []; @@ -347,7 +347,7 @@ function v35_default(name, version2, hashfunc) { } var DNS, URL2; var init_v35 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/v35.js"() { + "node_modules/uuid/dist/esm-node/v35.js"() { init_stringify(); init_parse(); DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"; @@ -355,7 +355,7 @@ var init_v35 = __esm({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/md5.js +// node_modules/uuid/dist/esm-node/md5.js function md5(bytes) { if (Array.isArray(bytes)) { bytes = Buffer.from(bytes); @@ -366,16 +366,16 @@ function md5(bytes) { } var import_crypto2, md5_default; var init_md5 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/md5.js"() { + "node_modules/uuid/dist/esm-node/md5.js"() { import_crypto2 = __toESM(require("crypto")); md5_default = md5; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/v3.js +// node_modules/uuid/dist/esm-node/v3.js var v3, v3_default; var init_v3 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/v3.js"() { + "node_modules/uuid/dist/esm-node/v3.js"() { init_v35(); init_md5(); v3 = v35_default("v3", 48, md5_default); @@ -383,7 +383,7 @@ var init_v3 = __esm({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/v4.js +// node_modules/uuid/dist/esm-node/v4.js function v4(options, buf, offset) { options = options || {}; const rnds = options.random || (options.rng || rng)(); @@ -400,14 +400,14 @@ function v4(options, buf, offset) { } var v4_default; var init_v4 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/v4.js"() { + "node_modules/uuid/dist/esm-node/v4.js"() { init_rng(); init_stringify(); v4_default = v4; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/sha1.js +// node_modules/uuid/dist/esm-node/sha1.js function sha1(bytes) { if (Array.isArray(bytes)) { bytes = Buffer.from(bytes); @@ -418,16 +418,16 @@ function sha1(bytes) { } var import_crypto3, sha1_default; var init_sha1 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/sha1.js"() { + "node_modules/uuid/dist/esm-node/sha1.js"() { import_crypto3 = __toESM(require("crypto")); sha1_default = sha1; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/v5.js +// node_modules/uuid/dist/esm-node/v5.js var v5, v5_default; var init_v5 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/v5.js"() { + "node_modules/uuid/dist/esm-node/v5.js"() { init_v35(); init_sha1(); v5 = v35_default("v5", 80, sha1_default); @@ -435,15 +435,15 @@ var init_v5 = __esm({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/nil.js +// node_modules/uuid/dist/esm-node/nil.js var nil_default; var init_nil = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/nil.js"() { + "node_modules/uuid/dist/esm-node/nil.js"() { nil_default = "00000000-0000-0000-0000-000000000000"; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/version.js +// node_modules/uuid/dist/esm-node/version.js function version(uuid) { if (!validate_default(uuid)) { throw TypeError("Invalid UUID"); @@ -452,13 +452,13 @@ function version(uuid) { } var version_default; var init_version = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/version.js"() { + "node_modules/uuid/dist/esm-node/version.js"() { init_validate(); version_default = version; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/index.js +// node_modules/uuid/dist/esm-node/index.js var esm_node_exports = {}; __export(esm_node_exports, { NIL: () => nil_default, @@ -472,7 +472,7 @@ __export(esm_node_exports, { version: () => version_default }); var init_esm_node = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/index.js"() { + "node_modules/uuid/dist/esm-node/index.js"() { init_v1(); init_v3(); init_v4(); diff --git a/modules/github-action/dist/run-task.cjs b/modules/github-action/dist/run-task.cjs index c2584c5f..67dcbd3e 100644 --- a/modules/github-action/dist/run-task.cjs +++ b/modules/github-action/dist/run-task.cjs @@ -152,7 +152,7 @@ var require_command = __commonJS({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/rng.js +// node_modules/uuid/dist/esm-node/rng.js function rng() { if (poolPtr > rnds8Pool.length - 16) { import_crypto.default.randomFillSync(rnds8Pool); @@ -162,34 +162,34 @@ function rng() { } var import_crypto, rnds8Pool, poolPtr; var init_rng = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/rng.js"() { + "node_modules/uuid/dist/esm-node/rng.js"() { import_crypto = __toESM(require("crypto")); rnds8Pool = new Uint8Array(256); poolPtr = rnds8Pool.length; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/regex.js +// node_modules/uuid/dist/esm-node/regex.js var regex_default; var init_regex = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/regex.js"() { + "node_modules/uuid/dist/esm-node/regex.js"() { regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/validate.js +// node_modules/uuid/dist/esm-node/validate.js function validate(uuid) { return typeof uuid === "string" && regex_default.test(uuid); } var validate_default; var init_validate = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/validate.js"() { + "node_modules/uuid/dist/esm-node/validate.js"() { init_regex(); validate_default = validate; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/stringify.js +// node_modules/uuid/dist/esm-node/stringify.js function stringify(arr, offset = 0) { const uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); if (!validate_default(uuid)) { @@ -199,7 +199,7 @@ function stringify(arr, offset = 0) { } var byteToHex, stringify_default; var init_stringify = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/stringify.js"() { + "node_modules/uuid/dist/esm-node/stringify.js"() { init_validate(); byteToHex = []; for (let i = 0; i < 256; ++i) { @@ -209,7 +209,7 @@ var init_stringify = __esm({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/v1.js +// node_modules/uuid/dist/esm-node/v1.js function v1(options, buf, offset) { let i = buf && offset || 0; const b = buf || new Array(16); @@ -260,7 +260,7 @@ function v1(options, buf, offset) { } var _nodeId, _clockseq, _lastMSecs, _lastNSecs, v1_default; var init_v1 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/v1.js"() { + "node_modules/uuid/dist/esm-node/v1.js"() { init_rng(); init_stringify(); _lastMSecs = 0; @@ -269,7 +269,7 @@ var init_v1 = __esm({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/parse.js +// node_modules/uuid/dist/esm-node/parse.js function parse(uuid) { if (!validate_default(uuid)) { throw TypeError("Invalid UUID"); @@ -296,13 +296,13 @@ function parse(uuid) { } var parse_default; var init_parse = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/parse.js"() { + "node_modules/uuid/dist/esm-node/parse.js"() { init_validate(); parse_default = parse; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/v35.js +// node_modules/uuid/dist/esm-node/v35.js function stringToBytes(str) { str = unescape(encodeURIComponent(str)); const bytes = []; @@ -347,7 +347,7 @@ function v35_default(name, version2, hashfunc) { } var DNS, URL2; var init_v35 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/v35.js"() { + "node_modules/uuid/dist/esm-node/v35.js"() { init_stringify(); init_parse(); DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"; @@ -355,7 +355,7 @@ var init_v35 = __esm({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/md5.js +// node_modules/uuid/dist/esm-node/md5.js function md5(bytes) { if (Array.isArray(bytes)) { bytes = Buffer.from(bytes); @@ -366,16 +366,16 @@ function md5(bytes) { } var import_crypto2, md5_default; var init_md5 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/md5.js"() { + "node_modules/uuid/dist/esm-node/md5.js"() { import_crypto2 = __toESM(require("crypto")); md5_default = md5; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/v3.js +// node_modules/uuid/dist/esm-node/v3.js var v3, v3_default; var init_v3 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/v3.js"() { + "node_modules/uuid/dist/esm-node/v3.js"() { init_v35(); init_md5(); v3 = v35_default("v3", 48, md5_default); @@ -383,7 +383,7 @@ var init_v3 = __esm({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/v4.js +// node_modules/uuid/dist/esm-node/v4.js function v4(options, buf, offset) { options = options || {}; const rnds = options.random || (options.rng || rng)(); @@ -400,14 +400,14 @@ function v4(options, buf, offset) { } var v4_default; var init_v4 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/v4.js"() { + "node_modules/uuid/dist/esm-node/v4.js"() { init_rng(); init_stringify(); v4_default = v4; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/sha1.js +// node_modules/uuid/dist/esm-node/sha1.js function sha1(bytes) { if (Array.isArray(bytes)) { bytes = Buffer.from(bytes); @@ -418,16 +418,16 @@ function sha1(bytes) { } var import_crypto3, sha1_default; var init_sha1 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/sha1.js"() { + "node_modules/uuid/dist/esm-node/sha1.js"() { import_crypto3 = __toESM(require("crypto")); sha1_default = sha1; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/v5.js +// node_modules/uuid/dist/esm-node/v5.js var v5, v5_default; var init_v5 = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/v5.js"() { + "node_modules/uuid/dist/esm-node/v5.js"() { init_v35(); init_sha1(); v5 = v35_default("v5", 80, sha1_default); @@ -435,15 +435,15 @@ var init_v5 = __esm({ } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/nil.js +// node_modules/uuid/dist/esm-node/nil.js var nil_default; var init_nil = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/nil.js"() { + "node_modules/uuid/dist/esm-node/nil.js"() { nil_default = "00000000-0000-0000-0000-000000000000"; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/version.js +// node_modules/uuid/dist/esm-node/version.js function version(uuid) { if (!validate_default(uuid)) { throw TypeError("Invalid UUID"); @@ -452,13 +452,13 @@ function version(uuid) { } var version_default; var init_version = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/version.js"() { + "node_modules/uuid/dist/esm-node/version.js"() { init_validate(); version_default = version; } }); -// node_modules/@actions/core/node_modules/uuid/dist/esm-node/index.js +// node_modules/uuid/dist/esm-node/index.js var esm_node_exports = {}; __export(esm_node_exports, { NIL: () => nil_default, @@ -472,7 +472,7 @@ __export(esm_node_exports, { version: () => version_default }); var init_esm_node = __esm({ - "node_modules/@actions/core/node_modules/uuid/dist/esm-node/index.js"() { + "node_modules/uuid/dist/esm-node/index.js"() { init_v1(); init_v3(); init_v4(); diff --git a/modules/logger/src/LogStep.ts b/modules/logger/src/LogStep.ts index 801233da..927ff060 100644 --- a/modules/logger/src/LogStep.ts +++ b/modules/logger/src/LogStep.ts @@ -45,9 +45,9 @@ export class LogStep extends Duplex { * @internal */ isPiped: boolean = false; + verbosity: Verbosity; #startMark: string; - #verbosity: Verbosity; #hasError: boolean = false; #hasWarning: boolean = false; @@ -60,7 +60,7 @@ export class LogStep extends Duplex { constructor(options: LogStepOptions) { const { description, name, verbosity } = options; super({ decodeStrings: false, objectMode: true }); - this.#verbosity = verbosity; + this.verbosity = verbosity; this.#startMark = name || `${performance.now()}`; performance.mark(`onerepo_start_${this.#startMark}`, { @@ -108,7 +108,7 @@ export class LogStep extends Duplex { * @internal */ _write( - chunk: string | Buffer, + chunk: string | LoggedBuffer, // eslint-disable-next-line @typescript-eslint/no-unused-vars encoding = 'utf8', callback: () => void, @@ -130,14 +130,10 @@ export class LogStep extends Duplex { type, contents: stringify(contents), group: this.name, - verbosity: this.#verbosity, + verbosity: this.verbosity, } satisfies LoggedBuffer); } - set verbosity(verbosity: Verbosity) { - this.#verbosity = verbosity; - } - /** * @internal */ @@ -378,7 +374,7 @@ export class LogStep extends Duplex { contents: stringify(contents), group: this.name, hasError: this.#hasError, - verbosity: this.#verbosity, + verbosity: this.verbosity, } satisfies LoggedBuffer, callback, ); diff --git a/modules/logger/src/Logger.ts b/modules/logger/src/Logger.ts index 10f2c160..a2400251 100644 --- a/modules/logger/src/Logger.ts +++ b/modules/logger/src/Logger.ts @@ -1,4 +1,5 @@ -import type { Writable } from 'node:stream'; +import type { Duplex, Transform, Writable } from 'node:stream'; +import { randomUUID } from 'node:crypto'; import { destroyCurrent, setCurrent } from './global'; import { LogStep } from './LogStep'; import { LogStepToString } from './transforms/LogStepToString'; @@ -39,7 +40,7 @@ export class Logger { #defaultLogger: LogStep; #steps: Array = []; #verbosity: Verbosity = 0; - #stream: Writable | LogStep; + #stream: Writable | Duplex | Transform | LogStep; #hasError = false; #hasWarning = false; @@ -48,6 +49,8 @@ export class Logger { #captureAll = false; + id: string; + /** * @internal */ @@ -56,6 +59,7 @@ export class Logger { this.#stream = options.stream ?? process.stderr; this.#captureAll = !!options.captureAll; this.verbosity = options.verbosity; + this.id = randomUUID(); setCurrent(this); } @@ -396,7 +400,7 @@ export class Logger { #activate(step: LogStep) { const activeStep = this.#steps.find((step) => step.isPiped); - if (activeStep) { + if (activeStep || step.isPiped) { return; } @@ -404,10 +408,6 @@ export class Logger { this.#defaultLogger.cork(); } - if (step.isPiped) { - return; - } - hideCursor(); if (!step.name || !(this.#stream as typeof process.stderr).isTTY) { @@ -434,10 +434,8 @@ export class Logger { this.#setState(step); step.unpipe(); - step.destroy(); step.isPiped = false; - // step.destroy(); - // await step.flush(); + step.destroy(); this.#defaultLogger.uncork(); @@ -453,12 +451,12 @@ export class Logger { } await new Promise((resolve) => { + // setTimeout(() => { setImmediate(() => { - setImmediate(() => { - this.#defaultLogger.cork(); - resolve(); - }); + this.#defaultLogger.cork(); + resolve(); }); + // }, 60); }); this.#activate(this.#steps[0]); diff --git a/modules/logger/src/global.ts b/modules/logger/src/global.ts index 2bba8578..0af5a456 100644 --- a/modules/logger/src/global.ts +++ b/modules/logger/src/global.ts @@ -1,15 +1,9 @@ import type { Logger } from './Logger'; -const sym = Symbol.for('onerepo_loggers'); +const loggers: Array = []; function getLoggers(): Array { - // @ts-ignore Cannot type symbol as key on global - if (!global[sym]) { - // @ts-ignore - global[sym] = []; - } - // @ts-ignore - return global[sym]; + return loggers; } export function getCurrent() { diff --git a/modules/logger/src/index.ts b/modules/logger/src/index.ts index 31274797..5a7d9836 100644 --- a/modules/logger/src/index.ts +++ b/modules/logger/src/index.ts @@ -1,4 +1,4 @@ -import type { Writable } from 'node:stream'; +import type { Writable, TransformOptions } from 'node:stream'; import { Transform } from 'node:stream'; import restoreCursorDefault from 'restore-cursor'; import { Logger } from './Logger'; @@ -73,7 +73,7 @@ export async function stepWrapper( const out = await fn(step); if (!inputStep) { - await step.end(); + step.end(); } return out; @@ -95,21 +95,24 @@ export async function stepWrapper( * @alpha * @group Logger */ -export function bufferSubLogger(step: Writable | LogStep): { logger: Logger; end: () => Promise } { - const logger = getLogger(); - const stream = new Buffered(); - const subLogger = new Logger({ verbosity: logger.verbosity, stream, captureAll: true }); - +export function bufferSubLogger(step: LogStep): { logger: Logger; end: () => Promise } { + const stream = new Buffered() as Transform; stream.pipe(step as Writable); + const subLogger = new Logger({ verbosity: step.verbosity, stream, captureAll: true }); + return { logger: subLogger, async end() { + // TODO: this promise/immediate may not be necessary await new Promise((resolve) => { setImmediate(async () => { stream.unpipe(); stream.destroy(); - resolve(); + if (subLogger.hasError) { + step.hasError = true; + } + return resolve(); }); }); }, @@ -119,6 +122,19 @@ export function bufferSubLogger(step: Writable | LogStep): { logger: Logger; end export const restoreCursor = restoreCursorDefault; class Buffered extends Transform { + constructor(opts?: TransformOptions) { + super(opts); + // We're going to be adding many listeners to this transform. This prevents warnings about _potential_ memory leaks + // However, we're (hopefully) managing piping, unpiping, and destroying the streams correctly in the Logger + this.setMaxListeners(0); + } + + // TODO: The buffered logger seems to have its `end()` method automatically called after the first step + // is ended. This is a complete mystery, but hacking it to not end saves it from prematurely closing + // before unpipe+destroy is called. + // @ts-expect-error + end() {} + _transform( chunk: Buffer, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -135,4 +151,9 @@ class Buffered extends Transform { ); callback(); } + + _final(callback: () => void) { + this.push(null); + callback(); + } } diff --git a/modules/logger/src/transforms/LogStepToString.ts b/modules/logger/src/transforms/LogStepToString.ts index c1ead42f..b648dc5f 100644 --- a/modules/logger/src/transforms/LogStepToString.ts +++ b/modules/logger/src/transforms/LogStepToString.ts @@ -9,13 +9,13 @@ export class LogStepToString extends Transform { } _transform( - chunk: LoggedBuffer | string, + chunk: LoggedBuffer | string | Buffer, // eslint-disable-next-line @typescript-eslint/no-unused-vars encoding = 'utf8', callback: () => void, ) { try { - if (typeof chunk === 'string') { + if (typeof chunk === 'string' || Buffer.isBuffer(chunk)) { this.push(chunk); } else { const data = chunk as LoggedBuffer; diff --git a/modules/onerepo/src/core/tasks/tasks.ts b/modules/onerepo/src/core/tasks/tasks.ts index 4c559a34..d3d1a6bf 100644 --- a/modules/onerepo/src/core/tasks/tasks.ts +++ b/modules/onerepo/src/core/tasks/tasks.ts @@ -134,7 +134,7 @@ export const handler: Handler = async (argv, { getWorkspaces, graph, logge if (list) { process.stdout.write(JSON.stringify({ parallel: [], serial: [] })); } - await setupStep.end(); + setupStep.end(); if (staged) { await stagingWorkflow.restoreUnstaged(); } @@ -180,7 +180,7 @@ export const handler: Handler = async (argv, { getWorkspaces, graph, logge } if (list) { - await setupStep.end(); + setupStep.end(); const step = logger.createStep('Listing tasks'); const all = { parallel: parallelTasks, @@ -196,19 +196,20 @@ export const handler: Handler = async (argv, { getWorkspaces, graph, logge return value; }), ); - await step.end(); + step.end(); return; } if (!hasTasks) { setupStep.info(`No tasks to run`); - await setupStep.end(); + setupStep.end(); if (staged) { await stagingWorkflow.restoreUnstaged(); } return; + } else { + setupStep.end(); } - await setupStep.end(); try { await batch(parallelTasks.flat(1).map((task) => task.fn ?? task)); diff --git a/modules/subprocess/src/index.ts b/modules/subprocess/src/index.ts index 18b96399..fa1ff3d5 100644 --- a/modules/subprocess/src/index.ts +++ b/modules/subprocess/src/index.ts @@ -378,19 +378,17 @@ export async function batch( return new Promise((resolve, reject) => { const logger = getLogger(); logger.debug(`Running ${tasks.length} processes with max parallelism ${maxParallel}`); - function runTask(runner: () => Promise<[string, string]>, index: number): Promise { - return runner() - .then((output) => { - results[index] = output; - }) - .catch((e) => { - failing = true; - results[index] = e; - }) - .finally(() => { - completed += 1; - runNextTask(); - }); + async function runTask(runner: () => Promise<[string, string]>, index: number): Promise { + try { + const output = await runner(); + results[index] = output; + } catch (e) { + failing = true; + results[index] = e as Error; + } finally { + completed += 1; + runNextTask(); + } } function runNextTask() { diff --git a/plugins/vitest/src/commands/vitest.ts b/plugins/vitest/src/commands/vitest.ts index 158dceaa..1369ee45 100644 --- a/plugins/vitest/src/commands/vitest.ts +++ b/plugins/vitest/src/commands/vitest.ts @@ -46,7 +46,7 @@ export const builder: Builder = (yargs) => export const handler: Handler = async function handler(argv, { getWorkspaces, graph }) { const { '--': other = [], affected, config, inspect, watch, workspaces } = argv; - const args: Array = ['--config', config]; + const args: Array = ['--config', config, '--clearScreen=false']; const wOther = other.indexOf('-w'); const watchOther = other.indexOf('--watch'); From 93085afdd465a36027c748c2078ef05f189e9692 Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Tue, 17 Dec 2024 18:07:32 -0800 Subject: [PATCH 15/17] fix(docs): bad rebase --- docs/src/content/docs/plugins/docgen.mdx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/src/content/docs/plugins/docgen.mdx b/docs/src/content/docs/plugins/docgen.mdx index 09d87bc2..b2639fb1 100644 --- a/docs/src/content/docs/plugins/docgen.mdx +++ b/docs/src/content/docs/plugins/docgen.mdx @@ -1,7 +1,4 @@ -<<<<<<< HEAD - -## <<<<<<< HEAD - +--- title: '@onerepo/plugin-docgen' sidebar: label: Documentation generation @@ -9,7 +6,6 @@ description: Official plugin for generating documentation in the oneRepo JavaScr meta: version: 1.0.1 stability: stable - --- # import { Tabs, TabItem } from '@astrojs/starlight/components'; From 10944e1e69d825f32ce10039cd3e55652a4ffc5c Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Tue, 17 Dec 2024 18:17:33 -0800 Subject: [PATCH 16/17] fix: lint --- modules/logger/src/__tests__/Logger.test.ts | 15 +++++++-------- modules/logger/src/transforms/LogStepToString.ts | 2 +- modules/subprocess/src/index.ts | 8 +++++--- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/modules/logger/src/__tests__/Logger.test.ts b/modules/logger/src/__tests__/Logger.test.ts index fc6fc07b..16ac75c1 100644 --- a/modules/logger/src/__tests__/Logger.test.ts +++ b/modules/logger/src/__tests__/Logger.test.ts @@ -43,13 +43,12 @@ describe('Logger', () => { const logger = new Logger({ verbosity, stream }); const logs = { - info: `${pc.blue(pc.bold('INFO'))} some information`, - error: `${pc.red(pc.bold('ERR'))} an error`, - warn: `${pc.yellow(pc.bold('WRN'))} a warning`, - // log: `${pc.cyan(pc.bold('LOG'))} a log`, - log: ' a log', - debug: `${pc.magenta(pc.bold('DBG'))} a debug`, - timing: `${pc.red('⏳')} foo → bar: 0ms`, + info: ` ${pc.blue(pc.bold('INFO '))} some information`, + error: ` ${pc.red(pc.bold('ERR '))} an error`, + warn: ` ${pc.yellow(pc.bold('WRN '))} a warning`, + log: ` ${pc.cyan(pc.bold('LOG '))} a log`, + debug: ` ${pc.magenta(pc.bold('DBG '))} a debug`, + timing: ` ${pc.red('⏳')} foo → bar: 0ms`, }; logger.info('some information'); @@ -74,7 +73,7 @@ describe('Logger', () => { }, ); - test.only('logs "completed" message', async () => { + test('logs "completed" message', async () => { let out = ''; const stream = new PassThrough(); stream.on('data', (chunk) => { diff --git a/modules/logger/src/transforms/LogStepToString.ts b/modules/logger/src/transforms/LogStepToString.ts index b648dc5f..8a4d1b8e 100644 --- a/modules/logger/src/transforms/LogStepToString.ts +++ b/modules/logger/src/transforms/LogStepToString.ts @@ -23,7 +23,7 @@ export class LogStepToString extends Transform { this.push(ensureNewline(`${this.#prefix(data.type, data.group, stringify(data.contents), data.hasError)}`)); } } - } catch (e) { + } catch { this.push(JSON.stringify(chunk)); } callback(); diff --git a/modules/subprocess/src/index.ts b/modules/subprocess/src/index.ts index fa1ff3d5..6f51ff77 100644 --- a/modules/subprocess/src/index.ts +++ b/modules/subprocess/src/index.ts @@ -158,7 +158,9 @@ ${JSON.stringify(withoutLogger, null, 2)}\n${process.env.ONEREPO_ROOT ?? process }); subprocess.on('error', (error) => { - !options.skipFailures && step.error(error); + if (!options.skipFailures) { + step.error(error); + } logger.unpause(); if (!inputStep) { step.end(); @@ -273,14 +275,14 @@ export async function sudo(options: Omit & { reason?: string }) `DRY-RUN command: sudo ${commandString}\n`, ); - await step.end(); + step.end(); return ['', '']; } return new Promise((resolve, reject) => { try { execSync('sudo -n true &> /dev/null'); - } catch (e) { + } catch { step.warn('Sudo permissions are required to continue!'); step.warn(options.reason ?? 'If prompted, please type your password and hit [RETURN].'); step.debug(`Sudo permissions are being requested to run the following: From a1af38a66bd7d10e3f07901552e2b54bff9db70f Mon Sep 17 00:00:00 2001 From: Paul Armstrong Date: Wed, 18 Dec 2024 07:20:46 -0800 Subject: [PATCH 17/17] feat(logger): outdent --- docs/src/content/docs/plugins/docgen.mdx | 40 +++++++++++++++++-- modules/logger/src/index.ts | 2 +- modules/logger/src/transforms/LogProgress.ts | 2 +- .../logger/src/transforms/LogStepToString.ts | 6 +-- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/docs/src/content/docs/plugins/docgen.mdx b/docs/src/content/docs/plugins/docgen.mdx index b2639fb1..7f52bed3 100644 --- a/docs/src/content/docs/plugins/docgen.mdx +++ b/docs/src/content/docs/plugins/docgen.mdx @@ -1,14 +1,46 @@ --- title: '@onerepo/plugin-docgen' sidebar: -label: Documentation generation + label: Documentation generation description: Official plugin for generating documentation in the oneRepo JavaScript and TypeScript monorepo toolchain. meta: -version: 1.0.1 -stability: stable + version: 1.0.1 + stability: stable --- -# import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +## Installation + + + + + ```sh title="Install via npm" + npm install --save-dev @onerepo/plugin-docgen + ``` + + + + + ```sh title="Install via Yarn" + yarn add --dev @onerepo/plugin-docgen + ``` + + + + + ```sh title="Install via pnpm" + pnpm install --save-dev @onerepo/plugin-docgen + ``` + + + + +:::tip[Example] +Check out our very own source level [example](example/) generated directly from the oneRepo source code using this plugin. +::: + +## Configuration {/* start-auto-generated-from-cli-docgen */} {/* @generated SignedSource<> */} diff --git a/modules/logger/src/index.ts b/modules/logger/src/index.ts index 5a7d9836..50eadc25 100644 --- a/modules/logger/src/index.ts +++ b/modules/logger/src/index.ts @@ -146,7 +146,7 @@ class Buffered extends Transform { .toString() .trim() .split('\n') - .map((line) => (line.startsWith(prefix.end) ? `${line}` : ` │ ${line.trim()}`)) + .map((line) => `│ ${line.trim()}`) .join('\n')}\n`, ); callback(); diff --git a/modules/logger/src/transforms/LogProgress.ts b/modules/logger/src/transforms/LogProgress.ts index 31dabc21..51181df7 100644 --- a/modules/logger/src/transforms/LogProgress.ts +++ b/modules/logger/src/transforms/LogProgress.ts @@ -16,7 +16,7 @@ export class LogProgress extends Transform { clearTimeout(this.#updaterTimeout); this.#updaterTimeout = setTimeout(() => { // this.push(new Error().stack); - this.write(` └ ${frames[this.#frame % frames.length]}`); + this.write(`└ ${frames[this.#frame % frames.length]}`); this.#written = true; this.#frame += 1; // this.#runUpdater(); diff --git a/modules/logger/src/transforms/LogStepToString.ts b/modules/logger/src/transforms/LogStepToString.ts index 8a4d1b8e..adb9a54c 100644 --- a/modules/logger/src/transforms/LogStepToString.ts +++ b/modules/logger/src/transforms/LogStepToString.ts @@ -43,7 +43,7 @@ export class LogStepToString extends Transform { } return output .split('\n') - .map((line) => ` ${group ? '│ ' : ''}${prefix[type]}${line}`) + .map((line) => `${group ? '│ ' : ''}${prefix[type]}${line}`) .join('\n'); } } @@ -60,8 +60,8 @@ const typeMinVerbosity: Record = { export const prefix: Record = { timing: pc.red('⏳'), - start: ' ┌ ', - end: ' └ ', + start: '┌ ', + end: '└ ', error: pc.red(pc.bold('ERR ')), warn: pc.yellow(pc.bold('WRN ')), log: pc.cyan(pc.bold('LOG ')),