diff --git a/.changeset/sharp-lemons-build.md b/.changeset/sharp-lemons-build.md new file mode 100644 index 00000000..8b951c78 --- /dev/null +++ b/.changeset/sharp-lemons-build.md @@ -0,0 +1,5 @@ +--- +"@clack/prompts": minor +--- + +Using the `group` method, task logs can now have groups which themselves can have scrolling windows of logs. diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index ffae42d9..3df38cdf 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -5,6 +5,9 @@ import color from 'picocolors'; export const unicode = isUnicodeSupported(); export const isCI = (): boolean => process.env.CI === 'true'; +export const isTTY = (output: Writable): boolean => { + return (output as Writable & { isTTY?: boolean }).isTTY === true; +}; export const unicodeOr = (c: string, fallback: string) => (unicode ? c : fallback); export const S_STEP_ACTIVE = unicodeOr('◆', '*'); export const S_STEP_CANCEL = unicodeOr('■', 'x'); diff --git a/packages/prompts/src/task-log.ts b/packages/prompts/src/task-log.ts index 6ea09aa0..e098e0b6 100644 --- a/packages/prompts/src/task-log.ts +++ b/packages/prompts/src/task-log.ts @@ -2,7 +2,13 @@ import type { Writable } from 'node:stream'; import { getColumns } from '@clack/core'; import color from 'picocolors'; import { erase } from 'sisteransi'; -import { type CommonOptions, S_BAR, S_STEP_SUBMIT, isCI as isCIFn } from './common.js'; +import { + type CommonOptions, + S_BAR, + S_STEP_SUBMIT, + isCI as isCIFn, + isTTY as isTTYFn, +} from './common.js'; import { log } from './log.js'; export interface TaskLogOptions extends CommonOptions { @@ -20,6 +26,16 @@ export interface TaskLogCompletionOptions { showLog?: boolean; } +interface BufferEntry { + header?: string; + value: string; + full: string; + result?: { + status: 'success' | 'error'; + message: string; + }; +} + /** * Renders a log which clears on success and remains on failure */ @@ -30,7 +46,7 @@ export const taskLog = (opts: TaskLogOptions) => { const spacing = opts.spacing ?? 1; const barSize = 3; const retainLog = opts.retainLog === true; - const isCI = isCIFn(); + const isTTY = !isCIFn() && isTTYFn(output); output.write(`${secondarySymbol}\n`); output.write(`${color.green(S_STEP_SUBMIT)} ${opts.title}\n`); @@ -38,25 +54,63 @@ export const taskLog = (opts: TaskLogOptions) => { output.write(`${secondarySymbol}\n`); } - let buffer = ''; - let fullBuffer = ''; + const buffers: BufferEntry[] = [ + { + value: '', + full: '', + }, + ]; let lastMessageWasRaw = false; const clear = (clearTitle: boolean): void => { - if (buffer.length === 0) { + if (buffers.length === 0) { return; } - const bufferHeight = buffer.split('\n').reduce((count, line) => { - if (line === '') { - return count + 1; + + let lines = 0; + + if (clearTitle) { + lines += spacing + 2; + } + + for (const buffer of buffers) { + const { value, result } = buffer; + let text = result?.message ?? value; + + if (text.length === 0) { + continue; } - return count + Math.ceil((line.length + barSize) / columns); - }, 0); - const lines = bufferHeight + 1 + (clearTitle ? spacing + 2 : 0); - output.write(erase.lines(lines)); + + if (result === undefined && buffer.header !== undefined && buffer.header !== '') { + text += `\n${buffer.header}`; + } + + const bufferHeight = text.split('\n').reduce((count, line) => { + if (line === '') { + return count + 1; + } + return count + Math.ceil((line.length + barSize) / columns); + }, 0); + + lines += bufferHeight; + } + + if (lines > 0) { + lines += 1; + output.write(erase.lines(lines)); + } }; - const printBuffer = (buf: string, messageSpacing?: number): void => { - log.message(buf.split('\n').map(color.dim), { + const printBuffer = (buffer: BufferEntry, messageSpacing?: number, full?: boolean): void => { + const messages = full ? `${buffer.full}\n${buffer.value}` : buffer.value; + if (buffer.header !== undefined && buffer.header !== '') { + log.message(buffer.header.split('\n').map(color.bold), { + output, + secondarySymbol, + symbol: secondarySymbol, + spacing: 0, + }); + } + log.message(messages.split('\n').map(color.dim), { output, secondarySymbol, symbol: secondarySymbol, @@ -64,35 +118,87 @@ export const taskLog = (opts: TaskLogOptions) => { }); }; const renderBuffer = (): void => { - if (retainLog === true && fullBuffer.length > 0) { - printBuffer(`${fullBuffer}\n${buffer}`); - } else { - printBuffer(buffer); + for (const buffer of buffers) { + const { header, value, full } = buffer; + if ((header === undefined || header.length === 0) && value.length === 0) { + continue; + } + printBuffer(buffer, undefined, retainLog === true && full.length > 0); } }; - - return { - message(msg: string, mopts?: TaskLogMessageOptions) { - clear(false); - if ((mopts?.raw !== true || !lastMessageWasRaw) && buffer !== '') { - buffer += '\n'; - } - buffer += msg; - lastMessageWasRaw = mopts?.raw === true; - if (opts.limit !== undefined) { - const lines = buffer.split('\n'); - const linesToRemove = lines.length - opts.limit; - if (linesToRemove > 0) { - const removedLines = lines.splice(0, linesToRemove); - if (retainLog) { - fullBuffer += (fullBuffer === '' ? '' : '\n') + removedLines.join('\n'); - } + const message = (buffer: BufferEntry, msg: string, mopts?: TaskLogMessageOptions) => { + clear(false); + if ((mopts?.raw !== true || !lastMessageWasRaw) && buffer.value !== '') { + buffer.value += '\n'; + } + buffer.value += msg; + lastMessageWasRaw = mopts?.raw === true; + if (opts.limit !== undefined) { + const lines = buffer.value.split('\n'); + const linesToRemove = lines.length - opts.limit; + if (linesToRemove > 0) { + const removedLines = lines.splice(0, linesToRemove); + if (retainLog) { + buffer.full += (buffer.full === '' ? '' : '\n') + removedLines.join('\n'); } - buffer = lines.join('\n'); } - if (!isCI) { + buffer.value = lines.join('\n'); + } + if (isTTY) { + printBuffers(); + } + }; + const printBuffers = (): void => { + for (const buffer of buffers) { + if (buffer.result) { + if (buffer.result.status === 'error') { + log.error(buffer.result.message, { output, secondarySymbol, spacing: 0 }); + } else { + log.success(buffer.result.message, { output, secondarySymbol, spacing: 0 }); + } + } else if (buffer.value !== '') { printBuffer(buffer, 0); } + } + }; + const completeBuffer = (buffer: BufferEntry, result: BufferEntry['result']): void => { + clear(false); + + buffer.result = result; + + if (isTTY) { + printBuffers(); + } + }; + + return { + message(msg: string, mopts?: TaskLogMessageOptions) { + message(buffers[0], msg, mopts); + }, + group(name: string) { + const buffer: BufferEntry = { + header: name, + value: '', + full: '', + }; + buffers.push(buffer); + return { + message(msg: string, mopts?: TaskLogMessageOptions) { + message(buffer, msg, mopts); + }, + error(message: string) { + completeBuffer(buffer, { + status: 'error', + message, + }); + }, + success(message: string) { + completeBuffer(buffer, { + status: 'success', + message, + }); + }, + }; }, error(message: string, opts?: TaskLogCompletionOptions): void { clear(true); @@ -101,7 +207,9 @@ export const taskLog = (opts: TaskLogOptions) => { renderBuffer(); } // clear buffer since error is an end state - buffer = fullBuffer = ''; + buffers.splice(1, buffers.length - 1); + buffers[0].value = ''; + buffers[0].full = ''; }, success(message: string, opts?: TaskLogCompletionOptions): void { clear(true); @@ -110,7 +218,9 @@ export const taskLog = (opts: TaskLogOptions) => { renderBuffer(); } // clear buffer since success is an end state - buffer = fullBuffer = ''; + buffers.splice(1, buffers.length - 1); + buffers[0].value = ''; + buffers[0].full = ''; }, }; }; diff --git a/packages/prompts/test/__snapshots__/task-log.test.ts.snap b/packages/prompts/test/__snapshots__/task-log.test.ts.snap index f44d30f7..b9d1fa58 100644 --- a/packages/prompts/test/__snapshots__/task-log.test.ts.snap +++ b/packages/prompts/test/__snapshots__/task-log.test.ts.snap @@ -46,6 +46,556 @@ exports[`taskLog (isCI = false) > error > renders output with message 1`] = ` ] `; +exports[`taskLog (isCI = false) > group > applies limit per group 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "│ Group 0 +", + "│ Group 0 line 0 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +", + "│ Group 1 +", + "│ Group 1 line 0 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +", + "│ Group 1 +", + "│ Group 1 line 0 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +", + "", + "│ Group 0 +", + "│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +", + "", + "│ Group 0 +", + "│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 1 +│ Group 1 line 2 +", + "", + "│ Group 0 +", + "│ Group 0 line 2 +│ Group 0 line 3 +", + "│ Group 1 +", + "│ Group 1 line 1 +│ Group 1 line 2 +", + "", + "│ Group 0 +", + "│ Group 0 line 2 +│ Group 0 line 3 +", + "│ Group 1 +", + "│ Group 1 line 2 +│ Group 1 line 3 +", + "", + "│ Group 0 +", + "│ Group 0 line 3 +│ Group 0 line 4 +", + "│ Group 1 +", + "│ Group 1 line 2 +│ Group 1 line 3 +", + "", + "│ Group 0 +", + "│ Group 0 line 3 +│ Group 0 line 4 +", + "│ Group 1 +", + "│ Group 1 line 3 +│ Group 1 line 4 +", +] +`; + +exports[`taskLog (isCI = false) > group > can render multiple groups of different sizes 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "│ Group 0 +", + "│ Group 0 line 0 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +│ Group 1 line 3 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +│ Group 1 line 3 +│ Group 1 line 4 +", +] +`; + +exports[`taskLog (isCI = false) > group > can render multiple groups of equal size 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "│ Group 0 +", + "│ Group 0 line 0 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +", + "│ Group 1 +", + "│ Group 1 line 0 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +", + "│ Group 1 +", + "│ Group 1 line 0 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +│ Group 0 line 3 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +│ Group 0 line 3 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +│ Group 1 line 3 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +│ Group 0 line 3 +│ Group 0 line 4 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +│ Group 1 line 3 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +│ Group 0 line 3 +│ Group 0 line 4 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +│ Group 1 line 3 +│ Group 1 line 4 +", +] +`; + +exports[`taskLog (isCI = false) > group > handles empty groups 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "◆ Group success! +", +] +`; + +exports[`taskLog (isCI = false) > group > renders error state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "│ Group 0 +", + "│ Group 0 line 0 +", + "", + "■ Group error! +", +] +`; + +exports[`taskLog (isCI = false) > group > renders group error state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "│ Group 0 +", + "│ Group 0 line 0 +", + "", + "■ Group error! +", +] +`; + +exports[`taskLog (isCI = false) > group > renders group success state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "│ Group 0 +", + "│ Group 0 line 0 +", + "", + "◆ Group success! +", +] +`; + +exports[`taskLog (isCI = false) > group > renders success state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "│ Group 0 +", + "│ Group 0 line 0 +", + "", + "◆ Group success! +", +] +`; + +exports[`taskLog (isCI = false) > group > showLog shows all groups in order 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "│ Group 0 +", + "│ Group 0 line 0 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +│ Group 1 line 3 +", + "", + "│ Group 0 +", + "│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +│ Group 1 line 3 +│ Group 1 line 4 +", + "", + "◆ Group 0 success! +", + "│ Group 1 +", + "│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +│ Group 1 line 3 +│ Group 1 line 4 +", + "", + "◆ Group 0 success! +", + "■ Group 1 error! +", + "", + "│ +■ overall error +", + "│ Group 0 +", + "│ +│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ +│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +│ Group 1 line 3 +│ Group 1 line 4 +", +] +`; + exports[`taskLog (isCI = false) > message > can write line by line 1`] = ` [ "│ @@ -105,8 +655,6 @@ exports[`taskLog (isCI = false) > message > prints empty lines 1`] = ` "◇ foo ", "│ -", - "│  ", "│ line 1 ", @@ -630,6 +1178,163 @@ exports[`taskLog (isCI = true) > error > renders output with message 1`] = ` ] `; +exports[`taskLog (isCI = true) > group > applies limit per group 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", + "", + "", + "", + "", + "", + "", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > group > can render multiple groups of different sizes 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", + "", + "", + "", + "", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > group > can render multiple groups of equal size 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", + "", + "", + "", + "", + "", + "", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > group > handles empty groups 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", +] +`; + +exports[`taskLog (isCI = true) > group > renders error state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", +] +`; + +exports[`taskLog (isCI = true) > group > renders group error state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", +] +`; + +exports[`taskLog (isCI = true) > group > renders group success state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", +] +`; + +exports[`taskLog (isCI = true) > group > renders success state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", +] +`; + +exports[`taskLog (isCI = true) > group > showLog shows all groups in order 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "│ +■ overall error +", + "│ Group 0 +", + "│ +│ Group 0 line 0 +│ Group 0 line 1 +│ Group 0 line 2 +", + "│ Group 1 +", + "│ +│ Group 1 line 0 +│ Group 1 line 1 +│ Group 1 line 2 +│ Group 1 line 3 +│ Group 1 line 4 +", +] +`; + exports[`taskLog (isCI = true) > message > can write line by line 1`] = ` [ "│ diff --git a/packages/prompts/test/task-log.test.ts b/packages/prompts/test/task-log.test.ts index 99c85b87..01104bf2 100644 --- a/packages/prompts/test/task-log.test.ts +++ b/packages/prompts/test/task-log.test.ts @@ -18,6 +18,7 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { beforeEach(() => { output = new MockWritable(); + output.isTTY = isCI === 'false'; input = new MockReadable(); }); @@ -125,7 +126,6 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { title: 'foo', }); - log.message(''); log.message('line 1'); log.message(''); log.message('line 3'); @@ -288,4 +288,149 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { }); }); }); + + describe('group', () => { + test('can render multiple groups of equal size', async () => { + const log = prompts.taskLog({ + title: 'Some log', + input, + output + }); + const group0 = log.group('Group 0'); + const group1 = log.group('Group 1'); + + for (let i = 0; i < 5; i++) { + group0.message(`Group 0 line ${i}`); + group1.message(`Group 1 line ${i}`); + } + + expect(output.buffer).toMatchSnapshot(); + }); + + test('can render multiple groups of different sizes', async () => { + const log = prompts.taskLog({ + title: 'Some log', + input, + output + }); + const group0 = log.group('Group 0'); + const group1 = log.group('Group 1'); + + for (let i = 0; i < 3; i++) { + group0.message(`Group 0 line ${i}`); + } + for (let i = 0; i < 5; i++) { + group1.message(`Group 1 line ${i}`); + } + + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders success state', async () => { + const log = prompts.taskLog({ + title: 'Some log', + input, + output + }); + const group = log.group('Group 0'); + group.message(`Group 0 line 0`); + group.success('Group success!'); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders error state', async () => { + const log = prompts.taskLog({ + title: 'Some log', + input, + output + }); + const group = log.group('Group 0'); + group.message(`Group 0 line 0`); + group.error('Group error!'); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('applies limit per group', async () => { + const log = prompts.taskLog({ + title: 'Some log', + input, + output, + limit: 2, + }); + const group0 = log.group('Group 0'); + const group1 = log.group('Group 1'); + + for (let i = 0; i < 5; i++) { + group0.message(`Group 0 line ${i}`); + group1.message(`Group 1 line ${i}`); + } + + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders group success state', async () => { + const log = prompts.taskLog({ + title: 'Some log', + input, + output + }); + const group = log.group('Group 0'); + group.message(`Group 0 line 0`); + group.success('Group success!'); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders group error state', async () => { + const log = prompts.taskLog({ + title: 'Some log', + input, + output + }); + const group = log.group('Group 0'); + group.message(`Group 0 line 0`); + group.error('Group error!'); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('showLog shows all groups in order', async () => { + const log = prompts.taskLog({ + title: 'Some log', + input, + output, + }); + const group0 = log.group('Group 0'); + const group1 = log.group('Group 1'); + + for (let i = 0; i < 3; i++) { + group0.message(`Group 0 line ${i}`); + } + for (let i = 0; i < 5; i++) { + group1.message(`Group 1 line ${i}`); + } + + group0.success('Group 0 success!'); + group1.error('Group 1 error!'); + + log.error('overall error', {showLog: true}); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('handles empty groups', async () => { + const log = prompts.taskLog({ + title: 'Some log', + input, + output + }); + const group = log.group('Group 0'); + + group.success('Group success!'); + + expect(output.buffer).toMatchSnapshot(); + }); + }); }); diff --git a/packages/prompts/test/test-utils.ts b/packages/prompts/test/test-utils.ts index b0bf1147..6439e227 100644 --- a/packages/prompts/test/test-utils.ts +++ b/packages/prompts/test/test-utils.ts @@ -2,6 +2,7 @@ import { Readable, Writable } from 'node:stream'; export class MockWritable extends Writable { public buffer: string[] = []; + public isTTY = false; _write( chunk: any,