diff --git a/.changeset/famous-turkeys-burn.md b/.changeset/famous-turkeys-burn.md new file mode 100644 index 00000000..4705b7ea --- /dev/null +++ b/.changeset/famous-turkeys-burn.md @@ -0,0 +1,5 @@ +--- +"@clack/prompts": minor +--- + +Added new `taskLog` prompt for log output which is cleared on success diff --git a/packages/prompts/README.md b/packages/prompts/README.md index 2e297ee6..12961db2 100644 --- a/packages/prompts/README.md +++ b/packages/prompts/README.md @@ -204,4 +204,26 @@ stream.error((function *() { yield 'Error!'; })()); stream.message((function *() { yield 'Hello'; yield ", World" })(), { symbol: color.cyan('~') }); ``` +### Task Log + +When executing a sub-process or a similar sub-task, `taskLog` can be used to render the output continuously and clear it at the end if it was successful. + +```js +import { taskLog } from '@clack/prompts'; + +const log = taskLog({ + message: 'Running npm install' +}); + +for await (const line of npmInstall()) { + log.message(line); +} + +if (success) { + log.success('Done!'); +} else { + log.error('Failed!'); +} +``` + ![clack-log-prompts](https://github.com/bombshell-dev/clack/blob/main/.github/assets/clack-logs.png) diff --git a/packages/prompts/src/__snapshots__/index.test.ts.snap b/packages/prompts/src/__snapshots__/index.test.ts.snap index 6437aefe..b4b5a947 100644 --- a/packages/prompts/src/__snapshots__/index.test.ts.snap +++ b/packages/prompts/src/__snapshots__/index.test.ts.snap @@ -1781,6 +1781,600 @@ exports[`prompts (isCI = false) > spinner > stop > renders submit symbol and sto ] `; +exports[`prompts (isCI = false) > taskLog > error > clears output if showLog = false 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ +■ some error! +", +] +`; + +exports[`prompts (isCI = false) > taskLog > error > renders output with message 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ +■ some error! +", + "│ +│ line 0 +│ line 1 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > message > can write line by line 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > message > can write multiple lines 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +│ line 1 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > message > enforces limit if set 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 1 +│ line 2 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > message > prints empty lines 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│  +", + "│ line 1 +", + "", + "│ line 1 +│  +", + "", + "│ line 1 +│  +│ line 3 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > message > raw = true appends message text until newline 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0still line 0 +", + "", + "│ line 0still line 0 +│ line 1 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > message > raw = true works when mixed with non-raw messages 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0still line 0 +", + "", + "│ line 0still line 0 +│ line 1 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > message > raw = true works when started with non-raw messages 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 0 +│ line 1still line 1 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > retainLog > error > outputs limited log with limit by default 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 1 +│ line 2 +", + "", + "│ line 2 +│ line 3 +", + "", + "│ +■ woo! +", + "│ +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > retainLog > error > retainLog = false outputs full log without limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 0 +│ line 1 +│ line 2 +", + "", + "│ line 0 +│ line 1 +│ line 2 +│ line 3 +", + "", + "│ +■ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > retainLog > error > retainLog = false outputs limited log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 1 +│ line 2 +", + "", + "│ line 2 +│ line 3 +", + "", + "│ +■ woo! +", + "│ +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > retainLog > error > retainLog = true outputs full log 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 0 +│ line 1 +│ line 2 +", + "", + "│ line 0 +│ line 1 +│ line 2 +│ line 3 +", + "", + "│ +■ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > retainLog > error > retainLog = true outputs full log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 1 +│ line 2 +", + "", + "│ line 2 +│ line 3 +", + "", + "│ +■ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > retainLog > success > outputs limited log with limit by default 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 1 +│ line 2 +", + "", + "│ line 2 +│ line 3 +", + "", + "│ +◆ woo! +", + "│ +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > retainLog > success > retainLog = false outputs full log without limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 0 +│ line 1 +│ line 2 +", + "", + "│ line 0 +│ line 1 +│ line 2 +│ line 3 +", + "", + "│ +◆ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > retainLog > success > retainLog = false outputs limited log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 1 +│ line 2 +", + "", + "│ line 2 +│ line 3 +", + "", + "│ +◆ woo! +", + "│ +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > retainLog > success > retainLog = true outputs full log 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 0 +│ line 1 +│ line 2 +", + "", + "│ line 0 +│ line 1 +│ line 2 +│ line 3 +", + "", + "│ +◆ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > retainLog > success > retainLog = true outputs full log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ line 1 +│ line 2 +", + "", + "│ line 2 +│ line 3 +", + "", + "│ +◆ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > success > clears output and renders message 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ +◆ success! +", +] +`; + +exports[`prompts (isCI = false) > taskLog > success > renders output if showLog = true 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "│ line 0 +", + "", + "│ line 0 +│ line 1 +", + "", + "│ +◆ success! +", + "│ +│ line 0 +│ line 1 +", +] +`; + +exports[`prompts (isCI = false) > taskLog > writes message header 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", +] +`; + exports[`prompts (isCI = false) > text > can cancel 1`] = ` [ "[?25l", @@ -3774,6 +4368,409 @@ exports[`prompts (isCI = true) > spinner > stop > renders submit symbol and stop ] `; +exports[`prompts (isCI = true) > taskLog > error > clears output if showLog = false 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "│ +■ some error! +", +] +`; + +exports[`prompts (isCI = true) > taskLog > error > renders output with message 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "│ +■ some error! +", + "│ +│ line 0 +│ line 1 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > message > can write line by line 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", +] +`; + +exports[`prompts (isCI = true) > taskLog > message > can write multiple lines 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", +] +`; + +exports[`prompts (isCI = true) > taskLog > message > enforces limit if set 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", +] +`; + +exports[`prompts (isCI = true) > taskLog > message > prints empty lines 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", +] +`; + +exports[`prompts (isCI = true) > taskLog > message > raw = true appends message text until newline 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", +] +`; + +exports[`prompts (isCI = true) > taskLog > message > raw = true works when mixed with non-raw messages 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", +] +`; + +exports[`prompts (isCI = true) > taskLog > message > raw = true works when started with non-raw messages 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", +] +`; + +exports[`prompts (isCI = true) > taskLog > retainLog > error > outputs limited log with limit by default 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +■ woo! +", + "│ +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > retainLog > error > retainLog = false outputs full log without limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +■ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > retainLog > error > retainLog = false outputs limited log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +■ woo! +", + "│ +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > retainLog > error > retainLog = true outputs full log 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +■ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > retainLog > error > retainLog = true outputs full log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +■ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > retainLog > success > outputs limited log with limit by default 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +◆ woo! +", + "│ +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > retainLog > success > retainLog = false outputs full log without limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +◆ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > retainLog > success > retainLog = false outputs limited log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +◆ woo! +", + "│ +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > retainLog > success > retainLog = true outputs full log 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +◆ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > retainLog > success > retainLog = true outputs full log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +◆ woo! +", + "│ +│ line 0 +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > success > clears output and renders message 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "│ +◆ success! +", +] +`; + +exports[`prompts (isCI = true) > taskLog > success > renders output if showLog = true 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "│ +◆ success! +", + "│ +│ line 0 +│ line 1 +", +] +`; + +exports[`prompts (isCI = true) > taskLog > writes message header 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", +] +`; + exports[`prompts (isCI = true) > text > can cancel 1`] = ` [ "[?25l", diff --git a/packages/prompts/src/index.test.ts b/packages/prompts/src/index.test.ts index 3a41d7db..0d28c0aa 100644 --- a/packages/prompts/src/index.test.ts +++ b/packages/prompts/src/index.test.ts @@ -1416,4 +1416,270 @@ describe.each(['true', 'false'])('prompts (isCI = %s)', (isCI) => { expect(output.buffer).toMatchSnapshot(); }); }); + + describe('taskLog', () => { + test('writes message header', () => { + prompts.taskLog({ + input, + output, + title: 'foo', + }); + + expect(output.buffer).toMatchSnapshot(); + }); + + describe('message', () => { + test('can write line by line', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + }); + + log.message('line 0'); + log.message('line 1'); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('can write multiple lines', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + }); + + log.message('line 0\nline 1'); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('enforces limit if set', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + limit: 2, + }); + + log.message('line 0'); + log.message('line 1'); + log.message('line 2'); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('raw = true appends message text until newline', async () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + }); + + log.message('line 0', { raw: true }); + log.message('still line 0', { raw: true }); + log.message('\nline 1', { raw: true }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('raw = true works when mixed with non-raw messages', async () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + }); + + log.message('line 0', { raw: true }); + log.message('still line 0', { raw: true }); + log.message('line 1'); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('raw = true works when started with non-raw messages', async () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + }); + + log.message('line 0'); + log.message('line 1', { raw: true }); + log.message('still line 1', { raw: true }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('prints empty lines', async () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + }); + + log.message(''); + log.message('line 1'); + log.message(''); + log.message('line 3'); + + expect(output.buffer).toMatchSnapshot(); + }); + }); + + describe('error', () => { + test('renders output with message', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + }); + + log.message('line 0'); + log.message('line 1'); + + log.error('some error!'); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('clears output if showLog = false', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + }); + + log.message('line 0'); + log.message('line 1'); + + log.error('some error!', { showLog: false }); + + expect(output.buffer).toMatchSnapshot(); + }); + }); + + describe('success', () => { + test('clears output and renders message', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + }); + + log.message('line 0'); + log.message('line 1'); + + log.success('success!'); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders output if showLog = true', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + }); + + log.message('line 0'); + log.message('line 1'); + + log.success('success!', { showLog: true }); + + expect(output.buffer).toMatchSnapshot(); + }); + }); + + describe('retainLog', () => { + describe.each(['error', 'success'] as const)('%s', (method) => { + test('retainLog = true outputs full log', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + retainLog: true, + }); + + for (let i = 0; i < 4; i++) { + log.message(`line ${i}`); + } + + log[method]('woo!', { showLog: true }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('retainLog = true outputs full log with limit', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + retainLog: true, + limit: 2, + }); + + for (let i = 0; i < 4; i++) { + log.message(`line ${i}`); + } + + log[method]('woo!', { showLog: true }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('retainLog = false outputs full log without limit', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + retainLog: false, + }); + + for (let i = 0; i < 4; i++) { + log.message(`line ${i}`); + } + + log[method]('woo!', { showLog: true }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('retainLog = false outputs limited log with limit', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + retainLog: false, + limit: 2, + }); + + for (let i = 0; i < 4; i++) { + log.message(`line ${i}`); + } + + log[method]('woo!', { showLog: true }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('outputs limited log with limit by default', () => { + const log = prompts.taskLog({ + input, + output, + title: 'foo', + limit: 2, + }); + + for (let i = 0; i < 4; i++) { + log.message(`line ${i}`); + } + + log[method]('woo!', { showLog: true }); + + expect(output.buffer).toMatchSnapshot(); + }); + }); + }); + }); }); diff --git a/packages/prompts/src/index.ts b/packages/prompts/src/index.ts index d49c735b..137f3750 100644 --- a/packages/prompts/src/index.ts +++ b/packages/prompts/src/index.ts @@ -63,6 +63,13 @@ const symbol = (state: State) => { } }; +const getColumns = (output: Writable): number => { + if (output instanceof WriteStream && output.columns) { + return output.columns; + } + return 80; +}; + interface LimitOptionsParams extends CommonOptions { options: TOption[]; maxItems: number | undefined; @@ -691,16 +698,38 @@ export const outro = (message = '', opts?: CommonOptions) => { export interface LogMessageOptions extends CommonOptions { symbol?: string; + spacing?: number; + secondarySymbol?: string; } export const log = { message: ( - message = '', - { symbol = color.gray(S_BAR), output = process.stdout }: LogMessageOptions = {} + message: string | string[] = [], + { + symbol = color.gray(S_BAR), + secondarySymbol = color.gray(S_BAR), + output = process.stdout, + spacing = 1, + }: LogMessageOptions = {} ) => { - const parts = [`${color.gray(S_BAR)}`]; - if (message) { - const [firstLine, ...lines] = message.split('\n'); - parts.push(`${symbol} ${firstLine}`, ...lines.map((ln) => `${color.gray(S_BAR)} ${ln}`)); + const parts: string[] = []; + for (let i = 0; i < spacing; i++) { + parts.push(`${secondarySymbol}`); + } + const messageParts = Array.isArray(message) ? message : message.split('\n'); + if (messageParts.length > 0) { + const [firstLine, ...lines] = messageParts; + if (firstLine.length > 0) { + parts.push(`${symbol} ${firstLine}`); + } else { + parts.push(symbol); + } + for (const ln of lines) { + if (ln.length > 0) { + parts.push(`${secondarySymbol} ${ln}`); + } else { + parts.push(secondarySymbol); + } + } } output.write(`${parts.join('\n')}\n`); }, @@ -1014,3 +1043,113 @@ export const tasks = async (tasks: Task[], opts?: CommonOptions) => { s.stop(result || task.title); } }; + +export interface TaskLogOptions extends CommonOptions { + title: string; + limit?: number; + spacing?: number; + retainLog?: boolean; +} + +export interface TaskLogMessageOptions { + raw?: boolean; +} + +export interface TaskLogCompletionOptions { + showLog?: boolean; +} + +/** + * Renders a log which clears on success and remains on failure + */ +export const taskLog = (opts: TaskLogOptions) => { + const output: Writable = opts.output ?? process.stdout; + const columns = getColumns(output); + const secondarySymbol = color.dim(S_BAR); + const spacing = opts.spacing ?? 1; + const barSize = 3; + const retainLog = opts.retainLog === true; + const isCI = process.env.CI === 'true'; + + output.write(`${secondarySymbol}\n`); + output.write(`${color.green(S_STEP_SUBMIT)} ${opts.title}\n`); + for (let i = 0; i < spacing; i++) { + output.write(`${secondarySymbol}\n`); + } + + let buffer = ''; + let fullBuffer = ''; + let lastMessageWasRaw = false; + + const clear = (clearTitle: boolean): void => { + if (buffer.length === 0) { + return; + } + const bufferHeight = buffer.split('\n').reduce((count, line) => { + if (line === '') { + return count + 1; + } + return count + Math.ceil((line.length + barSize) / columns); + }, 0); + const lines = bufferHeight + 1 + (clearTitle ? spacing + 2 : 0); + output.write(erase.lines(lines)); + }; + const printBuffer = (buf: string, messageSpacing?: number): void => { + log.message(buf.split('\n').map(color.dim), { + output, + secondarySymbol, + symbol: secondarySymbol, + spacing: messageSpacing ?? spacing, + }); + }; + const renderBuffer = (): void => { + if (retainLog === true && fullBuffer.length > 0) { + printBuffer(`${fullBuffer}\n${buffer}`); + } else { + printBuffer(buffer); + } + }; + + 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'); + } + } + buffer = lines.join('\n'); + } + if (!isCI) { + printBuffer(buffer, 0); + } + }, + error(message: string, opts?: TaskLogCompletionOptions): void { + clear(true); + log.error(message, { output, secondarySymbol, spacing: 1 }); + if (opts?.showLog !== false) { + renderBuffer(); + } + // clear buffer since error is an end state + buffer = fullBuffer = ''; + }, + success(message: string, opts?: TaskLogCompletionOptions): void { + clear(true); + log.success(message, { output, secondarySymbol, spacing: 1 }); + if (opts?.showLog === true) { + renderBuffer(); + } + // clear buffer since success is an end state + buffer = fullBuffer = ''; + }, + }; +};