ktx/packages/cli/src/prompt-navigation.ts
Andrey Avtomonov b690e6988b chore(workspace): gate dead-code with knip production mode
Turn on production-mode knip plus an autofix run in pre-commit and the
`pnpm dead-code` script, document the `/** @internal */` convention for
test-only exports in AGENTS.md, annotate test-only exports across the
CLI with that JSDoc, and drop dead exports/wrappers the new gate
surfaced (e.g. `cli-project.ts`, `lookerRuntimeSourceToFileAdapterSource`,
`createLocalScanEnrichmentProvidersFromConfig`,
`PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES`, stale type re-exports).
Replace the loose `ignoreIssues` allowlist in `knip.json` with explicit
production entries so cross-package barrel leaks are caught.
2026-05-21 11:35:41 +02:00

68 lines
2.3 KiB
TypeScript

const MULTISELECT_MENU_NAVIGATION_HINT =
'Use Up/Down to move, Space to select or unselect, Enter to confirm, Escape to go back, or Ctrl+C to exit.';
const TEXT_INPUT_NAVIGATION_HINT = 'Press Escape to go back.';
function removeTrailingBlankLines(message: string): string {
return message.replace(/\n+$/, '');
}
function prefixContinuationLines(message: string): string {
const lines = message.split('\n');
if (lines.length <= 1) return message;
const [title, ...body] = lines;
let trailingEmptyCount = 0;
while (trailingEmptyCount < body.length && body[body.length - 1 - trailingEmptyCount] === '') {
trailingEmptyCount++;
}
const contentBody = trailingEmptyCount > 0 ? body.slice(0, -trailingEmptyCount) : body;
const trailingBody = trailingEmptyCount > 0 ? body.slice(-trailingEmptyCount) : [];
return [
title,
...contentBody.map((line) => {
const stripped = line.replace(/^│\s*/, '');
return stripped === '' ? '│' : `${stripped}`;
}),
...trailingBody,
].join('\n');
}
function withTextInputBodySpacing(message: string): string {
const normalized = removeTrailingBlankLines(message);
if (!normalized.includes('\n')) {
return normalized;
}
const [title, ...bodyLines] = normalized.split('\n');
if (bodyLines[0] === '') {
return normalized;
}
return `${title}\n\n${bodyLines.join('\n')}`;
}
/** @internal */
export function withMenuOptionSpacing(message: string): string {
if (!message.includes('\n') || message.endsWith('\n')) {
return message;
}
return `${message}\n`;
}
export function withMenuOptionsSpacing<T extends { message: string }>(options: T): T {
return { ...options, message: withMenuOptionSpacing(options.message) };
}
export function withMultiselectNavigation(message: string): string {
if (message.includes(MULTISELECT_MENU_NAVIGATION_HINT)) {
return message;
}
return `${message}\n${MULTISELECT_MENU_NAVIGATION_HINT}`;
}
export function withTextInputNavigation(message: string): string {
const messageWithoutHint = removeTrailingBlankLines(message)
.split('\n')
.filter((line) => !line.includes(TEXT_INPUT_NAVIGATION_HINT))
.map((line) => line.replace(/^│\s*/, ''))
.join('\n');
const full = `${withTextInputBodySpacing(messageWithoutHint)}\n${TEXT_INPUT_NAVIGATION_HINT}`;
return `${prefixContinuationLines(full)}\n│`;
}