From 3d40389275b9afa60f670322d87dbc3a285d8a98 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 6 Nov 2025 16:25:47 +0000 Subject: [PATCH 1/8] feat: add includeBorder to all prompts --- packages/core/tsconfig.json | 2 +- packages/prompts/src/box.ts | 3 +- packages/prompts/src/common.ts | 1 + packages/prompts/src/log.ts | 17 +- .../test/__snapshots__/box.test.ts.snap | 312 +++++++++--------- .../test/__snapshots__/log.test.ts.snap | 211 ++++++++++++ packages/prompts/test/box.test.ts | 4 +- packages/prompts/test/log.test.ts | 139 ++++++++ packages/prompts/tsconfig.json | 2 +- tsconfig.json | 2 + 10 files changed, 526 insertions(+), 167 deletions(-) create mode 100644 packages/prompts/test/__snapshots__/log.test.ts.snap create mode 100644 packages/prompts/test/log.test.ts diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 596e2cf7..ef502e89 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src"] + "include": ["src", "test"] } diff --git a/packages/prompts/src/box.ts b/packages/prompts/src/box.ts index 060ada1e..a717ff0c 100644 --- a/packages/prompts/src/box.ts +++ b/packages/prompts/src/box.ts @@ -35,7 +35,6 @@ export interface BoxOptions extends CommonOptions { titlePadding?: number; contentPadding?: number; rounded?: boolean; - includePrefix?: boolean; formatBorder?: (text: string) => string; } @@ -68,7 +67,7 @@ export const box = (message = '', title = '', opts?: BoxOptions) => { const titlePadding = opts?.titlePadding ?? 1; const contentPadding = opts?.contentPadding ?? 2; const width = opts?.width === undefined || opts.width === 'auto' ? 1 : Math.min(1, opts.width); - const linePrefix = opts?.includePrefix ? `${S_BAR} ` : ''; + const linePrefix = opts?.withBorder === false ? '' : `${S_BAR} `; const formatBorder = opts?.formatBorder ?? defaultFormatBorder; const symbols = (opts?.rounded ? roundedSymbols : squareSymbols).map(formatBorder); const hSymbol = formatBorder(S_BAR_H); diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index 57670ab3..c86acc07 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -57,4 +57,5 @@ export interface CommonOptions { input?: Readable; output?: Writable; signal?: AbortSignal; + withBorder?: boolean; } diff --git a/packages/prompts/src/log.ts b/packages/prompts/src/log.ts index ff4e00cd..e2fbb146 100644 --- a/packages/prompts/src/log.ts +++ b/packages/prompts/src/log.ts @@ -23,25 +23,32 @@ export const log = { secondarySymbol = color.gray(S_BAR), output = process.stdout, spacing = 1, + withBorder, }: LogMessageOptions = {} ) => { const parts: string[] = []; + const isStandalone = withBorder === false; + const spacingString = isStandalone ? '' : secondarySymbol; + const prefix = isStandalone ? '' : `${symbol} `; + const secondaryPrefix = isStandalone ? '' : `${secondarySymbol} `; + for (let i = 0; i < spacing; i++) { - parts.push(`${secondarySymbol}`); + parts.push(spacingString); } + 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}`); + parts.push(`${prefix}${firstLine}`); } else { - parts.push(symbol); + parts.push(isStandalone ? '' : symbol); } for (const ln of lines) { if (ln.length > 0) { - parts.push(`${secondarySymbol} ${ln}`); + parts.push(`${secondaryPrefix}${ln}`); } else { - parts.push(secondarySymbol); + parts.push(isStandalone ? '' : secondarySymbol); } } } diff --git a/packages/prompts/test/__snapshots__/box.test.ts.snap b/packages/prompts/test/__snapshots__/box.test.ts.snap index 63f3b8c7..a9183551 100644 --- a/packages/prompts/test/__snapshots__/box.test.ts.snap +++ b/packages/prompts/test/__snapshots__/box.test.ts.snap @@ -2,528 +2,528 @@ exports[`box (isCI = false) > cannot have width larger than 100% 1`] = ` [ - "┌─title────────────────────────────────────────────────────────────────────────┐ + "│ ┌─title──────────────────────────────────────────────────────────────────────┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders as specified width 1`] = ` [ - "┌─title────────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ short │ + "│ │ short │ ", - "│ somewhat questionably long line │ + "│ │ somewhat questionably long line │ ", - "└──────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders as wide as longest line with width: auto 1`] = ` [ - "┌─title──────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ short │ + "│ │ short │ ", - "│ somewhat questionably long line │ + "│ │ somewhat questionably long line │ ", - "└────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders auto width with content longer than title 1`] = ` [ - "┌─title──────────────────────────┐ + "│ ┌─title──────────────────────────┐ ", - "│ messagemessagemessagemessage │ + "│ │ messagemessagemessagemessage │ ", - "└────────────────────────────────┘ + "│ └────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders auto width with title longer than content 1`] = ` [ - "┌─titletitletitletitle─┐ + "│ ┌─titletitletitletitle─┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────────────┘ + "│ └──────────────────────┘ ", ] `; exports[`box (isCI = false) > renders center aligned content 1`] = ` [ - "┌─title──────┐ + "│ ┌─title──────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders center aligned title 1`] = ` [ - "┌───title────┐ + "│ ┌───title────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders left aligned content 1`] = ` [ - "┌─title──────┐ + "│ ┌─title──────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders left aligned title 1`] = ` [ - "┌─title──────┐ + "│ ┌─title──────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders message 1`] = ` [ - "┌──────────────────────────────────────────────────────────────────────────────┐ + "│ ┌────────────────────────────────────────────────────────────────────────────┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders message with title 1`] = ` [ - "┌─some title───────────────────────────────────────────────────────────────────┐ + "│ ┌─some title─────────────────────────────────────────────────────────────────┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders right aligned content 1`] = ` [ - "┌─title──────┐ + "│ ┌─title──────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders right aligned title 1`] = ` [ - "┌──────title─┐ + "│ ┌──────title─┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders rounded corners when rounded is true 1`] = ` [ - "╭─title──────╮ + "│ ╭─title──────╮ ", - "│ message │ + "│ │ message │ ", - "╰────────────╯ + "│ ╰────────────╯ ", ] `; exports[`box (isCI = false) > renders specified contentPadding 1`] = ` [ - "┌─title──────────────┐ + "│ ┌─title──────────────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────────────┘ + "│ └────────────────────┘ ", ] `; exports[`box (isCI = false) > renders specified titlePadding 1`] = ` [ - "┌──────title───────┐ + "│ ┌──────title───────┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────────┘ + "│ └──────────────────┘ ", ] `; exports[`box (isCI = false) > renders truncated long titles 1`] = ` [ - "┌─foofoofoo...─┐ + "│ ┌─foofoof...─┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders wide characters with auto width 1`] = ` [ - "┌─这是标题─────────────────┐ + "│ ┌─这是标题─────────────────┐ ", - "│ 이게 첫 번째 줄이에요 │ + "│ │ 이게 첫 번째 줄이에요 │ ", - "│ これは次の行です │ + "│ │ これは次の行です │ ", - "└──────────────────────────┘ + "│ └──────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders wide characters with specified width 1`] = ` [ - "┌─这是标题─────┐ + "│ ┌─这是标题───┐ ", - "│ 이게 첫 │ + "│ │ 이게 첫 │ ", - "│ 번째 │ + "│ │ 번째 │ ", - "│ 줄이에요 │ + "│ │ 줄이에요 │ ", - "│ これは次の │ + "│ │ これは次 │ ", - "│ 行です │ + "│ │ の行です │ ", - "└──────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders with formatBorder formatting 1`] = ` [ - "┌─title──────┐ + "│ ┌─title──────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; -exports[`box (isCI = false) > renders with prefix when includePrefix is true 1`] = ` +exports[`box (isCI = false) > renders without left border when withBorder is false 1`] = ` [ - "│ ┌─title──────┐ + "┌─title──────┐ ", - "│ │ message │ + "│ message │ ", - "│ └────────────┘ + "└────────────┘ ", ] `; exports[`box (isCI = false) > wraps content to fit within specified width 1`] = ` [ - "┌─title────────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ foo barfoo barfoo barfoo barfoo │ + "│ │ foo barfoo barfoo barfoo barfoo │ ", - "│ barfoo barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ barfoo barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ barfoo bar │ + "│ │ barfoo barfoo barfoo bar │ ", - "└──────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > cannot have width larger than 100% 1`] = ` [ - "┌─title────────────────────────────────────────────────────────────────────────┐ + "│ ┌─title──────────────────────────────────────────────────────────────────────┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders as specified width 1`] = ` [ - "┌─title────────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ short │ + "│ │ short │ ", - "│ somewhat questionably long line │ + "│ │ somewhat questionably long line │ ", - "└──────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders as wide as longest line with width: auto 1`] = ` [ - "┌─title──────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ short │ + "│ │ short │ ", - "│ somewhat questionably long line │ + "│ │ somewhat questionably long line │ ", - "└────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders auto width with content longer than title 1`] = ` [ - "┌─title──────────────────────────┐ + "│ ┌─title──────────────────────────┐ ", - "│ messagemessagemessagemessage │ + "│ │ messagemessagemessagemessage │ ", - "└────────────────────────────────┘ + "│ └────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders auto width with title longer than content 1`] = ` [ - "┌─titletitletitletitle─┐ + "│ ┌─titletitletitletitle─┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────────────┘ + "│ └──────────────────────┘ ", ] `; exports[`box (isCI = true) > renders center aligned content 1`] = ` [ - "┌─title──────┐ + "│ ┌─title──────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders center aligned title 1`] = ` [ - "┌───title────┐ + "│ ┌───title────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders left aligned content 1`] = ` [ - "┌─title──────┐ + "│ ┌─title──────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders left aligned title 1`] = ` [ - "┌─title──────┐ + "│ ┌─title──────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders message 1`] = ` [ - "┌──────────────────────────────────────────────────────────────────────────────┐ + "│ ┌────────────────────────────────────────────────────────────────────────────┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders message with title 1`] = ` [ - "┌─some title───────────────────────────────────────────────────────────────────┐ + "│ ┌─some title─────────────────────────────────────────────────────────────────┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders right aligned content 1`] = ` [ - "┌─title──────┐ + "│ ┌─title──────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders right aligned title 1`] = ` [ - "┌──────title─┐ + "│ ┌──────title─┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders rounded corners when rounded is true 1`] = ` [ - "╭─title──────╮ + "│ ╭─title──────╮ ", - "│ message │ + "│ │ message │ ", - "╰────────────╯ + "│ ╰────────────╯ ", ] `; exports[`box (isCI = true) > renders specified contentPadding 1`] = ` [ - "┌─title──────────────┐ + "│ ┌─title──────────────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────────────┘ + "│ └────────────────────┘ ", ] `; exports[`box (isCI = true) > renders specified titlePadding 1`] = ` [ - "┌──────title───────┐ + "│ ┌──────title───────┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────────┘ + "│ └──────────────────┘ ", ] `; exports[`box (isCI = true) > renders truncated long titles 1`] = ` [ - "┌─foofoofoo...─┐ + "│ ┌─foofoof...─┐ ", - "│ message │ + "│ │ message │ ", - "└──────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders wide characters with auto width 1`] = ` [ - "┌─这是标题─────────────────┐ + "│ ┌─这是标题─────────────────┐ ", - "│ 이게 첫 번째 줄이에요 │ + "│ │ 이게 첫 번째 줄이에요 │ ", - "│ これは次の行です │ + "│ │ これは次の行です │ ", - "└──────────────────────────┘ + "│ └──────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders wide characters with specified width 1`] = ` [ - "┌─这是标题─────┐ + "│ ┌─这是标题───┐ ", - "│ 이게 첫 │ + "│ │ 이게 첫 │ ", - "│ 번째 │ + "│ │ 번째 │ ", - "│ 줄이에요 │ + "│ │ 줄이에요 │ ", - "│ これは次の │ + "│ │ これは次 │ ", - "│ 行です │ + "│ │ の行です │ ", - "└──────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders with formatBorder formatting 1`] = ` [ - "┌─title──────┐ + "│ ┌─title──────┐ ", - "│ message │ + "│ │ message │ ", - "└────────────┘ + "│ └────────────┘ ", ] `; -exports[`box (isCI = true) > renders with prefix when includePrefix is true 1`] = ` +exports[`box (isCI = true) > renders without left border when withBorder is false 1`] = ` [ - "│ ┌─title──────┐ + "┌─title──────┐ ", - "│ │ message │ + "│ message │ ", - "│ └────────────┘ + "└────────────┘ ", ] `; exports[`box (isCI = true) > wraps content to fit within specified width 1`] = ` [ - "┌─title────────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ foo barfoo barfoo barfoo barfoo │ + "│ │ foo barfoo barfoo barfoo barfoo │ ", - "│ barfoo barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ barfoo barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ barfoo bar │ + "│ │ barfoo barfoo barfoo bar │ ", - "└──────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; diff --git a/packages/prompts/test/__snapshots__/log.test.ts.snap b/packages/prompts/test/__snapshots__/log.test.ts.snap new file mode 100644 index 00000000..d47a437e --- /dev/null +++ b/packages/prompts/test/__snapshots__/log.test.ts.snap @@ -0,0 +1,211 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`log (isCI = false) > error > renders error message 1`] = ` +[ + "│ +■ error message +", +] +`; + +exports[`log (isCI = false) > info > renders info message 1`] = ` +[ + "│ +● info message +", +] +`; + +exports[`log (isCI = false) > message > renders message 1`] = ` +[ + "│ +│ message +", +] +`; + +exports[`log (isCI = false) > message > renders message from array 1`] = ` +[ + "│ +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`log (isCI = false) > message > renders message with custom spacing 1`] = ` +[ + "│ +│ +│ +│ spaced message +", +] +`; + +exports[`log (isCI = false) > message > renders message with custom symbols and spacing 1`] = ` +[ + "-- +>> custom +-- symbols +", +] +`; + +exports[`log (isCI = false) > message > renders multiline message 1`] = ` +[ + "│ +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`log (isCI = false) > message > renders multiline standalone message 1`] = ` +[ + " +line 1 +line 2 +line 3 +", +] +`; + +exports[`log (isCI = false) > message > renders standalone message 1`] = ` +[ + " +standalone message +", +] +`; + +exports[`log (isCI = false) > step > renders step message 1`] = ` +[ + "│ +◇ step message +", +] +`; + +exports[`log (isCI = false) > success > renders success message 1`] = ` +[ + "│ +◆ success message +", +] +`; + +exports[`log (isCI = false) > warn > renders warn message 1`] = ` +[ + "│ +▲ warn message +", +] +`; + +exports[`log (isCI = true) > error > renders error message 1`] = ` +[ + "│ +■ error message +", +] +`; + +exports[`log (isCI = true) > info > renders info message 1`] = ` +[ + "│ +● info message +", +] +`; + +exports[`log (isCI = true) > message > renders message 1`] = ` +[ + "│ +│ message +", +] +`; + +exports[`log (isCI = true) > message > renders message from array 1`] = ` +[ + "│ +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`log (isCI = true) > message > renders message with custom spacing 1`] = ` +[ + "│ +│ +│ +│ spaced message +", +] +`; + +exports[`log (isCI = true) > message > renders message with custom symbols and spacing 1`] = ` +[ + "-- +>> custom +-- symbols +", +] +`; + +exports[`log (isCI = true) > message > renders multiline message 1`] = ` +[ + "│ +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`log (isCI = true) > message > renders multiline standalone message 1`] = ` +[ + " +line 1 +line 2 +line 3 +", +] +`; + +exports[`log (isCI = true) > message > renders standalone message 1`] = ` +[ + " +standalone message +", +] +`; + +exports[`log (isCI = true) > step > renders step message 1`] = ` +[ + "│ +◇ step message +", +] +`; + +exports[`log (isCI = true) > success > renders success message 1`] = ` +[ + "│ +◆ success message +", +] +`; + +exports[`log (isCI = true) > warn > renders warn message 1`] = ` +[ + "│ +▲ warn message +", +] +`; diff --git a/packages/prompts/test/box.test.ts b/packages/prompts/test/box.test.ts index 31105984..0eb3e1d6 100644 --- a/packages/prompts/test/box.test.ts +++ b/packages/prompts/test/box.test.ts @@ -96,11 +96,11 @@ describe.each(['true', 'false'])('box (isCI = %s)', (isCI) => { expect(output.buffer).toMatchSnapshot(); }); - test('renders with prefix when includePrefix is true', () => { + test('renders without left border when withBorder is false', () => { prompts.box('message', 'title', { input, output, - includePrefix: true, + withBorder: false, width: 'auto', }); diff --git a/packages/prompts/test/log.test.ts b/packages/prompts/test/log.test.ts new file mode 100644 index 00000000..bf90ec0f --- /dev/null +++ b/packages/prompts/test/log.test.ts @@ -0,0 +1,139 @@ +import colors from 'picocolors'; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; +import * as prompts from '../src/index.js'; +import { MockWritable } from './test-utils.js'; + +describe.each(['true', 'false'])('log (isCI = %s)', (isCI) => { + let originalCI: string | undefined; + let output: MockWritable; + + beforeAll(() => { + originalCI = process.env.CI; + process.env.CI = isCI; + }); + + afterAll(() => { + process.env.CI = originalCI; + }); + + beforeEach(() => { + output = new MockWritable(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('message', () => { + test('renders message', () => { + prompts.log.message('message', { + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders multiline message', () => { + prompts.log.message('line 1\nline 2\nline 3', { + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders message from array', () => { + prompts.log.message(['line 1', 'line 2', 'line 3'], { + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders message with custom symbols and spacing', () => { + prompts.log.message('custom\nsymbols', { + symbol: colors.red('>>'), + secondarySymbol: colors.yellow('--'), + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders standalone message', () => { + prompts.log.message('standalone message', { + withBorder: false, + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders multiline standalone message', () => { + prompts.log.message('line 1\nline 2\nline 3', { + withBorder: false, + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders message with custom spacing', () => { + prompts.log.message('spaced message', { + spacing: 3, + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + }); + + describe('info', () => { + test('renders info message', () => { + prompts.log.info('info message', { + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + }); + + describe('success', () => { + test('renders success message', () => { + prompts.log.success('success message', { + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + }); + + describe('step', () => { + test('renders step message', () => { + prompts.log.step('step message', { + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + }); + + describe('warn', () => { + test('renders warn message', () => { + prompts.log.warn('warn message', { + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + }); + + describe('error', () => { + test('renders error message', () => { + prompts.log.error('error message', { + output, + }); + + expect(output.buffer).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/prompts/tsconfig.json b/packages/prompts/tsconfig.json index 596e2cf7..ef502e89 100644 --- a/packages/prompts/tsconfig.json +++ b/packages/prompts/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src"] + "include": ["src", "test"] } diff --git a/tsconfig.json b/tsconfig.json index 5350b4a8..1b74280c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,8 @@ "skipLibCheck": true, "isolatedModules": true, "verbatimModuleSyntax": true, + "noUnusedParameters": true, + "noUnusedLocals": true, "lib": ["ES2022"], "paths": { "@clack/core": ["./packages/core/src/index.ts"], From bee79dd5ac41eedce132769029c79e42b82e523c Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 6 Nov 2025 16:36:46 +0000 Subject: [PATCH 2/8] feat: add withBorder to text input --- packages/prompts/src/text.ts | 24 ++++++++----- .../test/__snapshots__/text.test.ts.snap | 34 +++++++++++++++++++ packages/prompts/test/text.test.ts | 15 ++++++++ 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 244f7c8d..920d3be0 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -20,7 +20,9 @@ export const text = (opts: TextOptions) => { signal: opts.signal, input: opts.input, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const withBorder = opts.withBorder !== false; + const titlePrefix = withBorder ? `${color.gray(S_BAR)}\n${symbol(this.state)} ` : ''; + const title = `${titlePrefix}${opts.message}\n`; const placeholder = opts.placeholder ? color.inverse(opts.placeholder[0]) + color.dim(opts.placeholder.slice(1)) : color.inverse(color.hidden('_')); @@ -30,20 +32,26 @@ export const text = (opts: TextOptions) => { switch (this.state) { case 'error': { const errorText = this.error ? ` ${color.yellow(this.error)}` : ''; - return `${title.trim()}\n${color.yellow(S_BAR)} ${userInput}\n${color.yellow( - S_BAR_END - )}${errorText}\n`; + const errorPrefix = withBorder ? `${color.yellow(S_BAR)} ` : ''; + const errorPrefixEnd = withBorder ? color.yellow(S_BAR_END) : ''; + return `${title.trim()}\n${errorPrefix}${userInput}\n${errorPrefixEnd}${errorText}\n`; } case 'submit': { const valueText = value ? ` ${color.dim(value)}` : ''; - return `${title}${color.gray(S_BAR)}${valueText}`; + const submitPrefix = withBorder ? color.gray(S_BAR) : ''; + return `${title}${submitPrefix}${valueText}`; } case 'cancel': { const valueText = value ? ` ${color.strikethrough(color.dim(value))}` : ''; - return `${title}${color.gray(S_BAR)}${valueText}${value.trim() ? `\n${color.gray(S_BAR)}` : ''}`; + const cancelPrefix = withBorder ? color.gray(S_BAR) : ''; + return `${title}${cancelPrefix}${valueText}${value.trim() ? `\n${cancelPrefix}` : ''}`; + } + default: { + const defaultPrefix = withBorder ? `${color.cyan(S_BAR)} ` : ''; + const defaultPrefixEnd = withBorder ? color.cyan(S_BAR_END) : ''; + color.cyan(S_BAR_END); + return `${title}${defaultPrefix}${userInput}\n${defaultPrefixEnd}\n`; } - default: - return `${title}${color.cyan(S_BAR)} ${userInput}\n${color.cyan(S_BAR_END)}\n`; } }, }).prompt() as Promise; diff --git a/packages/prompts/test/__snapshots__/text.test.ts.snap b/packages/prompts/test/__snapshots__/text.test.ts.snap index 29abf5b4..e9a655c3 100644 --- a/packages/prompts/test/__snapshots__/text.test.ts.snap +++ b/packages/prompts/test/__snapshots__/text.test.ts.snap @@ -263,6 +263,23 @@ exports[`text (isCI = false) > validation errors render and clear 1`] = ` ] `; +exports[`text (isCI = false) > withBorder: false removes borders 1`] = ` +[ + "", + "foo +_ + +", + "", + "", + "", + "", + " +", + "", +] +`; + exports[`text (isCI = true) > can be aborted by a signal 1`] = ` [ "", @@ -525,3 +542,20 @@ exports[`text (isCI = true) > validation errors render and clear 1`] = ` "", ] `; + +exports[`text (isCI = true) > withBorder: false removes borders 1`] = ` +[ + "", + "foo +_ + +", + "", + "", + "", + "", + " +", + "", +] +`; diff --git a/packages/prompts/test/text.test.ts b/packages/prompts/test/text.test.ts index 13a00bab..fd37bf66 100644 --- a/packages/prompts/test/text.test.ts +++ b/packages/prompts/test/text.test.ts @@ -205,4 +205,19 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { expect(prompts.isCancel(value)).toBe(true); expect(output.buffer).toMatchSnapshot(); }); + + test('withBorder: false removes borders', async () => { + const result = prompts.text({ + message: 'foo', + withBorder: false, + input, + output, + }); + + input.emit('keypress', '', { name: 'return' }); + + await result; + + expect(output.buffer).toMatchSnapshot(); + }); }); From f8bf95aad256fe72cb4a1b9622ee7c3f242174df Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 6 Nov 2025 16:39:33 +0000 Subject: [PATCH 3/8] chore: add changeset --- .changeset/tall-keys-allow.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/tall-keys-allow.md diff --git a/.changeset/tall-keys-allow.md b/.changeset/tall-keys-allow.md new file mode 100644 index 00000000..9e4d4a7d --- /dev/null +++ b/.changeset/tall-keys-allow.md @@ -0,0 +1,6 @@ +--- +"@clack/prompts": patch +"@clack/core": patch +--- + +Add a new `withBorder` option to all prompts to disable the default clack border From 4fc50b09cbd845d5ff48d097d3a579c2a5d124cb Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:57:20 +0000 Subject: [PATCH 4/8] chore: rename to withGuide --- .changeset/tall-keys-allow.md | 2 +- packages/prompts/src/box.ts | 2 +- packages/prompts/src/common.ts | 2 +- packages/prompts/src/log.ts | 14 ++++---- packages/prompts/src/text.ts | 16 ++++----- .../test/__snapshots__/box.test.ts.snap | 4 +-- .../test/__snapshots__/log.test.ts.snap | 36 +++++++++---------- .../test/__snapshots__/text.test.ts.snap | 4 +-- packages/prompts/test/box.test.ts | 4 +-- packages/prompts/test/log.test.ts | 8 ++--- packages/prompts/test/text.test.ts | 4 +-- 11 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.changeset/tall-keys-allow.md b/.changeset/tall-keys-allow.md index 9e4d4a7d..5d5566ce 100644 --- a/.changeset/tall-keys-allow.md +++ b/.changeset/tall-keys-allow.md @@ -3,4 +3,4 @@ "@clack/core": patch --- -Add a new `withBorder` option to all prompts to disable the default clack border +Add a new `withGuide` option to all prompts to disable the default clack border diff --git a/packages/prompts/src/box.ts b/packages/prompts/src/box.ts index a717ff0c..26d4878a 100644 --- a/packages/prompts/src/box.ts +++ b/packages/prompts/src/box.ts @@ -67,7 +67,7 @@ export const box = (message = '', title = '', opts?: BoxOptions) => { const titlePadding = opts?.titlePadding ?? 1; const contentPadding = opts?.contentPadding ?? 2; const width = opts?.width === undefined || opts.width === 'auto' ? 1 : Math.min(1, opts.width); - const linePrefix = opts?.withBorder === false ? '' : `${S_BAR} `; + const linePrefix = opts?.withGuide === false ? '' : `${S_BAR} `; const formatBorder = opts?.formatBorder ?? defaultFormatBorder; const symbols = (opts?.rounded ? roundedSymbols : squareSymbols).map(formatBorder); const hSymbol = formatBorder(S_BAR_H); diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index c86acc07..7d0a5c5a 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -57,5 +57,5 @@ export interface CommonOptions { input?: Readable; output?: Writable; signal?: AbortSignal; - withBorder?: boolean; + withGuide?: boolean; } diff --git a/packages/prompts/src/log.ts b/packages/prompts/src/log.ts index e2fbb146..798fc682 100644 --- a/packages/prompts/src/log.ts +++ b/packages/prompts/src/log.ts @@ -23,14 +23,14 @@ export const log = { secondarySymbol = color.gray(S_BAR), output = process.stdout, spacing = 1, - withBorder, + withGuide, }: LogMessageOptions = {} ) => { const parts: string[] = []; - const isStandalone = withBorder === false; - const spacingString = isStandalone ? '' : secondarySymbol; - const prefix = isStandalone ? '' : `${symbol} `; - const secondaryPrefix = isStandalone ? '' : `${secondarySymbol} `; + const hasGuide = withGuide === false; + const spacingString = hasGuide ? '' : secondarySymbol; + const prefix = hasGuide ? '' : `${symbol} `; + const secondaryPrefix = hasGuide ? '' : `${secondarySymbol} `; for (let i = 0; i < spacing; i++) { parts.push(spacingString); @@ -42,13 +42,13 @@ export const log = { if (firstLine.length > 0) { parts.push(`${prefix}${firstLine}`); } else { - parts.push(isStandalone ? '' : symbol); + parts.push(hasGuide ? '' : symbol); } for (const ln of lines) { if (ln.length > 0) { parts.push(`${secondaryPrefix}${ln}`); } else { - parts.push(isStandalone ? '' : secondarySymbol); + parts.push(hasGuide ? '' : secondarySymbol); } } } diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 920d3be0..a008a4a9 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -20,8 +20,8 @@ export const text = (opts: TextOptions) => { signal: opts.signal, input: opts.input, render() { - const withBorder = opts.withBorder !== false; - const titlePrefix = withBorder ? `${color.gray(S_BAR)}\n${symbol(this.state)} ` : ''; + const withGuide = opts.withGuide !== false; + const titlePrefix = withGuide ? `${color.gray(S_BAR)}\n${symbol(this.state)} ` : ''; const title = `${titlePrefix}${opts.message}\n`; const placeholder = opts.placeholder ? color.inverse(opts.placeholder[0]) + color.dim(opts.placeholder.slice(1)) @@ -32,23 +32,23 @@ export const text = (opts: TextOptions) => { switch (this.state) { case 'error': { const errorText = this.error ? ` ${color.yellow(this.error)}` : ''; - const errorPrefix = withBorder ? `${color.yellow(S_BAR)} ` : ''; - const errorPrefixEnd = withBorder ? color.yellow(S_BAR_END) : ''; + const errorPrefix = withGuide ? `${color.yellow(S_BAR)} ` : ''; + const errorPrefixEnd = withGuide ? color.yellow(S_BAR_END) : ''; return `${title.trim()}\n${errorPrefix}${userInput}\n${errorPrefixEnd}${errorText}\n`; } case 'submit': { const valueText = value ? ` ${color.dim(value)}` : ''; - const submitPrefix = withBorder ? color.gray(S_BAR) : ''; + const submitPrefix = withGuide ? color.gray(S_BAR) : ''; return `${title}${submitPrefix}${valueText}`; } case 'cancel': { const valueText = value ? ` ${color.strikethrough(color.dim(value))}` : ''; - const cancelPrefix = withBorder ? color.gray(S_BAR) : ''; + const cancelPrefix = withGuide ? color.gray(S_BAR) : ''; return `${title}${cancelPrefix}${valueText}${value.trim() ? `\n${cancelPrefix}` : ''}`; } default: { - const defaultPrefix = withBorder ? `${color.cyan(S_BAR)} ` : ''; - const defaultPrefixEnd = withBorder ? color.cyan(S_BAR_END) : ''; + const defaultPrefix = withGuide ? `${color.cyan(S_BAR)} ` : ''; + const defaultPrefixEnd = withGuide ? color.cyan(S_BAR_END) : ''; color.cyan(S_BAR_END); return `${title}${defaultPrefix}${userInput}\n${defaultPrefixEnd}\n`; } diff --git a/packages/prompts/test/__snapshots__/box.test.ts.snap b/packages/prompts/test/__snapshots__/box.test.ts.snap index a9183551..a24a65c2 100644 --- a/packages/prompts/test/__snapshots__/box.test.ts.snap +++ b/packages/prompts/test/__snapshots__/box.test.ts.snap @@ -234,7 +234,7 @@ exports[`box (isCI = false) > renders with formatBorder formatting 1`] = ` ] `; -exports[`box (isCI = false) > renders without left border when withBorder is false 1`] = ` +exports[`box (isCI = false) > renders without guide when withGuide is false 1`] = ` [ "┌─title──────┐ ", @@ -498,7 +498,7 @@ exports[`box (isCI = true) > renders with formatBorder formatting 1`] = ` ] `; -exports[`box (isCI = true) > renders without left border when withBorder is false 1`] = ` +exports[`box (isCI = true) > renders without guide when withGuide is false 1`] = ` [ "┌─title──────┐ ", diff --git a/packages/prompts/test/__snapshots__/log.test.ts.snap b/packages/prompts/test/__snapshots__/log.test.ts.snap index d47a437e..2bc884ed 100644 --- a/packages/prompts/test/__snapshots__/log.test.ts.snap +++ b/packages/prompts/test/__snapshots__/log.test.ts.snap @@ -53,6 +53,14 @@ exports[`log (isCI = false) > message > renders message with custom symbols and ] `; +exports[`log (isCI = false) > message > renders message with guide disabled 1`] = ` +[ + " +standalone message +", +] +`; + exports[`log (isCI = false) > message > renders multiline message 1`] = ` [ "│ @@ -63,7 +71,7 @@ exports[`log (isCI = false) > message > renders multiline message 1`] = ` ] `; -exports[`log (isCI = false) > message > renders multiline standalone message 1`] = ` +exports[`log (isCI = false) > message > renders multiline message with guide disabled 1`] = ` [ " line 1 @@ -73,14 +81,6 @@ line 3 ] `; -exports[`log (isCI = false) > message > renders standalone message 1`] = ` -[ - " -standalone message -", -] -`; - exports[`log (isCI = false) > step > renders step message 1`] = ` [ "│ @@ -158,6 +158,14 @@ exports[`log (isCI = true) > message > renders message with custom symbols and s ] `; +exports[`log (isCI = true) > message > renders message with guide disabled 1`] = ` +[ + " +standalone message +", +] +`; + exports[`log (isCI = true) > message > renders multiline message 1`] = ` [ "│ @@ -168,7 +176,7 @@ exports[`log (isCI = true) > message > renders multiline message 1`] = ` ] `; -exports[`log (isCI = true) > message > renders multiline standalone message 1`] = ` +exports[`log (isCI = true) > message > renders multiline message with guide disabled 1`] = ` [ " line 1 @@ -178,14 +186,6 @@ line 3 ] `; -exports[`log (isCI = true) > message > renders standalone message 1`] = ` -[ - " -standalone message -", -] -`; - exports[`log (isCI = true) > step > renders step message 1`] = ` [ "│ diff --git a/packages/prompts/test/__snapshots__/text.test.ts.snap b/packages/prompts/test/__snapshots__/text.test.ts.snap index e9a655c3..760ef296 100644 --- a/packages/prompts/test/__snapshots__/text.test.ts.snap +++ b/packages/prompts/test/__snapshots__/text.test.ts.snap @@ -263,7 +263,7 @@ exports[`text (isCI = false) > validation errors render and clear 1`] = ` ] `; -exports[`text (isCI = false) > withBorder: false removes borders 1`] = ` +exports[`text (isCI = false) > withGuide: false removes guide 1`] = ` [ "", "foo @@ -543,7 +543,7 @@ exports[`text (isCI = true) > validation errors render and clear 1`] = ` ] `; -exports[`text (isCI = true) > withBorder: false removes borders 1`] = ` +exports[`text (isCI = true) > withGuide: false removes guide 1`] = ` [ "", "foo diff --git a/packages/prompts/test/box.test.ts b/packages/prompts/test/box.test.ts index 0eb3e1d6..3ba05b2f 100644 --- a/packages/prompts/test/box.test.ts +++ b/packages/prompts/test/box.test.ts @@ -96,11 +96,11 @@ describe.each(['true', 'false'])('box (isCI = %s)', (isCI) => { expect(output.buffer).toMatchSnapshot(); }); - test('renders without left border when withBorder is false', () => { + test('renders without guide when withGuide is false', () => { prompts.box('message', 'title', { input, output, - withBorder: false, + withGuide: false, width: 'auto', }); diff --git a/packages/prompts/test/log.test.ts b/packages/prompts/test/log.test.ts index bf90ec0f..57fa0d42 100644 --- a/packages/prompts/test/log.test.ts +++ b/packages/prompts/test/log.test.ts @@ -59,18 +59,18 @@ describe.each(['true', 'false'])('log (isCI = %s)', (isCI) => { expect(output.buffer).toMatchSnapshot(); }); - test('renders standalone message', () => { + test('renders message with guide disabled', () => { prompts.log.message('standalone message', { - withBorder: false, + withGuide: false, output, }); expect(output.buffer).toMatchSnapshot(); }); - test('renders multiline standalone message', () => { + test('renders multiline message with guide disabled', () => { prompts.log.message('line 1\nline 2\nline 3', { - withBorder: false, + withGuide: false, output, }); diff --git a/packages/prompts/test/text.test.ts b/packages/prompts/test/text.test.ts index fd37bf66..e8f78bdd 100644 --- a/packages/prompts/test/text.test.ts +++ b/packages/prompts/test/text.test.ts @@ -206,10 +206,10 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { expect(output.buffer).toMatchSnapshot(); }); - test('withBorder: false removes borders', async () => { + test('withGuide: false removes guide', async () => { const result = prompts.text({ message: 'foo', - withBorder: false, + withGuide: false, input, output, }); From 2d0be2f8d2fe39a50333d6d1ffe831ea9065f6f0 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:01:01 +0000 Subject: [PATCH 5/8] chore: add prefix back to text prompts --- packages/prompts/src/text.ts | 2 +- .../prompts/test/__snapshots__/text.test.ts.snap | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index a008a4a9..79cd3282 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -21,7 +21,7 @@ export const text = (opts: TextOptions) => { input: opts.input, render() { const withGuide = opts.withGuide !== false; - const titlePrefix = withGuide ? `${color.gray(S_BAR)}\n${symbol(this.state)} ` : ''; + const titlePrefix = `${withGuide ? `${color.gray(S_BAR)}\n` : ''}${symbol(this.state)} `; const title = `${titlePrefix}${opts.message}\n`; const placeholder = opts.placeholder ? color.inverse(opts.placeholder[0]) + color.dim(opts.placeholder.slice(1)) diff --git a/packages/prompts/test/__snapshots__/text.test.ts.snap b/packages/prompts/test/__snapshots__/text.test.ts.snap index 760ef296..bfa9bcfe 100644 --- a/packages/prompts/test/__snapshots__/text.test.ts.snap +++ b/packages/prompts/test/__snapshots__/text.test.ts.snap @@ -266,14 +266,15 @@ exports[`text (isCI = false) > validation errors render and clear 1`] = ` exports[`text (isCI = false) > withGuide: false removes guide 1`] = ` [ "", - "foo + "◆ foo _ ", "", - "", - "", "", + "", + "◇ foo +", " ", "", @@ -546,14 +547,15 @@ exports[`text (isCI = true) > validation errors render and clear 1`] = ` exports[`text (isCI = true) > withGuide: false removes guide 1`] = ` [ "", - "foo + "◆ foo _ ", "", - "", - "", "", + "", + "◇ foo +", " ", "", From 18e611594a974b51143a2fa8335be85234ace3b5 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Fri, 21 Nov 2025 10:21:51 +0000 Subject: [PATCH 6/8] feat: add global withGuide --- packages/core/src/utils/settings.ts | 8 +++++ packages/prompts/src/box.ts | 5 +-- packages/prompts/src/log.ts | 9 ++--- packages/prompts/src/text.ts | 18 +++++----- .../test/__snapshots__/box.test.ts.snap | 22 ++++++++++++ .../test/__snapshots__/text.test.ts.snap | 36 +++++++++++++++++++ packages/prompts/test/box.test.ts | 14 ++++++++ packages/prompts/test/text.test.ts | 18 ++++++++++ 8 files changed, 115 insertions(+), 15 deletions(-) diff --git a/packages/core/src/utils/settings.ts b/packages/core/src/utils/settings.ts index 9ebb7fdb..7a2d880a 100644 --- a/packages/core/src/utils/settings.ts +++ b/packages/core/src/utils/settings.ts @@ -9,6 +9,7 @@ interface InternalClackSettings { cancel: string; error: string; }; + withGuide: boolean; } export const settings: InternalClackSettings = { @@ -27,6 +28,7 @@ export const settings: InternalClackSettings = { cancel: 'Canceled', error: 'Something went wrong', }, + withGuide: true, }; export interface ClackSettings { @@ -54,6 +56,8 @@ export interface ClackSettings { */ error?: string; }; + + withGuide?: boolean; } export function updateSettings(updates: ClackSettings) { @@ -81,6 +85,10 @@ export function updateSettings(updates: ClackSettings) { settings.messages.error = messages.error; } } + + if (updates.withGuide !== undefined) { + settings.withGuide = updates.withGuide !== false; + } } /** diff --git a/packages/prompts/src/box.ts b/packages/prompts/src/box.ts index 26d4878a..af299172 100644 --- a/packages/prompts/src/box.ts +++ b/packages/prompts/src/box.ts @@ -1,5 +1,5 @@ import type { Writable } from 'node:stream'; -import { getColumns } from '@clack/core'; +import { getColumns, settings } from '@clack/core'; import stringWidth from 'fast-string-width'; import { wrapAnsi } from 'fast-wrap-ansi'; import { @@ -67,7 +67,8 @@ export const box = (message = '', title = '', opts?: BoxOptions) => { const titlePadding = opts?.titlePadding ?? 1; const contentPadding = opts?.contentPadding ?? 2; const width = opts?.width === undefined || opts.width === 'auto' ? 1 : Math.min(1, opts.width); - const linePrefix = opts?.withGuide === false ? '' : `${S_BAR} `; + const hasGuide = (opts?.withGuide ?? settings.withGuide) !== false; + const linePrefix = !hasGuide ? '' : `${S_BAR} `; const formatBorder = opts?.formatBorder ?? defaultFormatBorder; const symbols = (opts?.rounded ? roundedSymbols : squareSymbols).map(formatBorder); const hSymbol = formatBorder(S_BAR_H); diff --git a/packages/prompts/src/log.ts b/packages/prompts/src/log.ts index 798fc682..6a896d58 100644 --- a/packages/prompts/src/log.ts +++ b/packages/prompts/src/log.ts @@ -1,3 +1,4 @@ +import { settings } from '@clack/core'; import color from 'picocolors'; import { type CommonOptions, @@ -27,10 +28,10 @@ export const log = { }: LogMessageOptions = {} ) => { const parts: string[] = []; - const hasGuide = withGuide === false; - const spacingString = hasGuide ? '' : secondarySymbol; - const prefix = hasGuide ? '' : `${symbol} `; - const secondaryPrefix = hasGuide ? '' : `${secondarySymbol} `; + const hasGuide = (withGuide ?? settings.withGuide) !== false; + const spacingString = !hasGuide ? '' : secondarySymbol; + const prefix = !hasGuide ? '' : `${symbol} `; + const secondaryPrefix = !hasGuide ? '' : `${secondarySymbol} `; for (let i = 0; i < spacing; i++) { parts.push(spacingString); diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 79cd3282..33a4c778 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -1,4 +1,4 @@ -import { TextPrompt } from '@clack/core'; +import { settings, TextPrompt } from '@clack/core'; import color from 'picocolors'; import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; @@ -20,8 +20,8 @@ export const text = (opts: TextOptions) => { signal: opts.signal, input: opts.input, render() { - const withGuide = opts.withGuide !== false; - const titlePrefix = `${withGuide ? `${color.gray(S_BAR)}\n` : ''}${symbol(this.state)} `; + const hasGuide = (opts?.withGuide ?? settings.withGuide) !== false; + const titlePrefix = `${hasGuide ? `${color.gray(S_BAR)}\n` : ''}${symbol(this.state)} `; const title = `${titlePrefix}${opts.message}\n`; const placeholder = opts.placeholder ? color.inverse(opts.placeholder[0]) + color.dim(opts.placeholder.slice(1)) @@ -32,23 +32,23 @@ export const text = (opts: TextOptions) => { switch (this.state) { case 'error': { const errorText = this.error ? ` ${color.yellow(this.error)}` : ''; - const errorPrefix = withGuide ? `${color.yellow(S_BAR)} ` : ''; - const errorPrefixEnd = withGuide ? color.yellow(S_BAR_END) : ''; + const errorPrefix = hasGuide ? `${color.yellow(S_BAR)} ` : ''; + const errorPrefixEnd = hasGuide ? color.yellow(S_BAR_END) : ''; return `${title.trim()}\n${errorPrefix}${userInput}\n${errorPrefixEnd}${errorText}\n`; } case 'submit': { const valueText = value ? ` ${color.dim(value)}` : ''; - const submitPrefix = withGuide ? color.gray(S_BAR) : ''; + const submitPrefix = hasGuide ? color.gray(S_BAR) : ''; return `${title}${submitPrefix}${valueText}`; } case 'cancel': { const valueText = value ? ` ${color.strikethrough(color.dim(value))}` : ''; - const cancelPrefix = withGuide ? color.gray(S_BAR) : ''; + const cancelPrefix = hasGuide ? color.gray(S_BAR) : ''; return `${title}${cancelPrefix}${valueText}${value.trim() ? `\n${cancelPrefix}` : ''}`; } default: { - const defaultPrefix = withGuide ? `${color.cyan(S_BAR)} ` : ''; - const defaultPrefixEnd = withGuide ? color.cyan(S_BAR_END) : ''; + const defaultPrefix = hasGuide ? `${color.cyan(S_BAR)} ` : ''; + const defaultPrefixEnd = hasGuide ? color.cyan(S_BAR_END) : ''; color.cyan(S_BAR_END); return `${title}${defaultPrefix}${userInput}\n${defaultPrefixEnd}\n`; } diff --git a/packages/prompts/test/__snapshots__/box.test.ts.snap b/packages/prompts/test/__snapshots__/box.test.ts.snap index a24a65c2..b8926767 100644 --- a/packages/prompts/test/__snapshots__/box.test.ts.snap +++ b/packages/prompts/test/__snapshots__/box.test.ts.snap @@ -234,6 +234,17 @@ exports[`box (isCI = false) > renders with formatBorder formatting 1`] = ` ] `; +exports[`box (isCI = false) > renders without guide when global withGuide is false 1`] = ` +[ + "┌─title──────┐ +", + "│ message │ +", + "└────────────┘ +", +] +`; + exports[`box (isCI = false) > renders without guide when withGuide is false 1`] = ` [ "┌─title──────┐ @@ -498,6 +509,17 @@ exports[`box (isCI = true) > renders with formatBorder formatting 1`] = ` ] `; +exports[`box (isCI = true) > renders without guide when global withGuide is false 1`] = ` +[ + "┌─title──────┐ +", + "│ message │ +", + "└────────────┘ +", +] +`; + exports[`box (isCI = true) > renders without guide when withGuide is false 1`] = ` [ "┌─title──────┐ diff --git a/packages/prompts/test/__snapshots__/text.test.ts.snap b/packages/prompts/test/__snapshots__/text.test.ts.snap index bfa9bcfe..0ffe0acc 100644 --- a/packages/prompts/test/__snapshots__/text.test.ts.snap +++ b/packages/prompts/test/__snapshots__/text.test.ts.snap @@ -71,6 +71,24 @@ exports[`text (isCI = false) > empty string when no value and no default 1`] = ` ] `; +exports[`text (isCI = false) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "", + "◇ foo +", + " +", + "", +] +`; + exports[`text (isCI = false) > placeholder is not used as value when pressing enter 1`] = ` [ "", @@ -352,6 +370,24 @@ exports[`text (isCI = true) > empty string when no value and no default 1`] = ` ] `; +exports[`text (isCI = true) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "", + "◇ foo +", + " +", + "", +] +`; + exports[`text (isCI = true) > placeholder is not used as value when pressing enter 1`] = ` [ "", diff --git a/packages/prompts/test/box.test.ts b/packages/prompts/test/box.test.ts index 3ba05b2f..faa34a4d 100644 --- a/packages/prompts/test/box.test.ts +++ b/packages/prompts/test/box.test.ts @@ -1,3 +1,4 @@ +import { updateSettings } from '@clack/core'; import colors from 'picocolors'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; import * as prompts from '../src/index.js'; @@ -24,6 +25,7 @@ describe.each(['true', 'false'])('box (isCI = %s)', (isCI) => { afterEach(() => { vi.restoreAllMocks(); + updateSettings({ withGuide: true }); }); test('renders message', () => { @@ -107,6 +109,18 @@ describe.each(['true', 'false'])('box (isCI = %s)', (isCI) => { expect(output.buffer).toMatchSnapshot(); }); + test('renders without guide when global withGuide is false', () => { + updateSettings({ withGuide: false }); + + prompts.box('message', 'title', { + input, + output, + width: 'auto', + }); + + expect(output.buffer).toMatchSnapshot(); + }); + test('renders truncated long titles', () => { prompts.box('message', 'foo'.repeat(20), { input, diff --git a/packages/prompts/test/text.test.ts b/packages/prompts/test/text.test.ts index e8f78bdd..62de9067 100644 --- a/packages/prompts/test/text.test.ts +++ b/packages/prompts/test/text.test.ts @@ -1,3 +1,4 @@ +import { updateSettings } from '@clack/core'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; import * as prompts from '../src/index.js'; import { MockReadable, MockWritable } from './test-utils.js'; @@ -23,6 +24,7 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { afterEach(() => { vi.restoreAllMocks(); + updateSettings({ withGuide: true }); }); test('renders message', async () => { @@ -220,4 +222,20 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { expect(output.buffer).toMatchSnapshot(); }); + + test('global withGuide: false removes guide', async () => { + updateSettings({ withGuide: false }); + + const result = prompts.text({ + message: 'foo', + input, + output, + }); + + input.emit('keypress', '', { name: 'return' }); + + await result; + + expect(output.buffer).toMatchSnapshot(); + }); }); From 15055e25ae72144aa83283db8c28589b304c74e8 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Fri, 21 Nov 2025 10:47:32 +0000 Subject: [PATCH 7/8] chore: remove dead code --- packages/prompts/src/text.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 33a4c778..2c3dbc8c 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -49,7 +49,6 @@ export const text = (opts: TextOptions) => { default: { const defaultPrefix = hasGuide ? `${color.cyan(S_BAR)} ` : ''; const defaultPrefixEnd = hasGuide ? color.cyan(S_BAR_END) : ''; - color.cyan(S_BAR_END); return `${title}${defaultPrefix}${userInput}\n${defaultPrefixEnd}\n`; } } From 0cf0e1f6d4cfce119797eab31317721d9bd5d328 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:10:06 +0000 Subject: [PATCH 8/8] chore: whatever happened here --- packages/prompts/test/__snapshots__/text.test.ts.snap | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/prompts/test/__snapshots__/text.test.ts.snap b/packages/prompts/test/__snapshots__/text.test.ts.snap index 0ffe0acc..43c38c78 100644 --- a/packages/prompts/test/__snapshots__/text.test.ts.snap +++ b/packages/prompts/test/__snapshots__/text.test.ts.snap @@ -79,7 +79,6 @@ exports[`text (isCI = false) > global withGuide: false removes guide 1`] = ` ", "", - "", "", "◇ foo ", @@ -289,7 +288,6 @@ exports[`text (isCI = false) > withGuide: false removes guide 1`] = ` ", "", - "", "", "◇ foo ", @@ -378,7 +376,6 @@ exports[`text (isCI = true) > global withGuide: false removes guide 1`] = ` ", "", - "", "", "◇ foo ", @@ -588,7 +585,6 @@ exports[`text (isCI = true) > withGuide: false removes guide 1`] = ` ", "", - "", "", "◇ foo ",