(final = false)
| 86 | let renderedLines = 0; |
| 87 | |
| 88 | function render(final = false) { |
| 89 | // Clear previous render |
| 90 | if (renderedLines > 0) { |
| 91 | stdout.write(`\x1B[${renderedLines}A`); // Move up |
| 92 | stdout.write('\x1B[0J'); // Clear from cursor to end |
| 93 | } |
| 94 | |
| 95 | const lines: string[] = []; |
| 96 | |
| 97 | if (final) { |
| 98 | lines.push(`${pc.green('◇')} Bump levels selected`); |
| 99 | const selected = displayOrder.filter(({ idx }) => levels[idx] !== 'skip'); |
| 100 | if (selected.length === 0) { |
| 101 | lines.push(`${pc.dim('│')} ${pc.dim('(none selected)')}`); |
| 102 | } else { |
| 103 | for (const { item, idx } of selected) { |
| 104 | lines.push(`${pc.dim('│')} ${pc.cyan(item.name)} ${pc.dim('→')} ${pc.bold(levels[idx]!)}`); |
| 105 | } |
| 106 | } |
| 107 | lines.push(pc.dim('│')); |
| 108 | |
| 109 | const output = lines.join('\n') + '\n'; |
| 110 | stdout.write(output); |
| 111 | renderedLines = lines.length; |
| 112 | return; |
| 113 | } |
| 114 | |
| 115 | const headerChrome = [ |
| 116 | `${pc.cyan('◆')} Select bump levels`, |
| 117 | `${pc.dim('│')} ${pc.dim('↑/↓ navigate · ←/→ change level · enter to confirm')}`, |
| 118 | `${pc.dim('│')} ${pc.dim('0 skip current · x skip all · r reset all to defaults')}`, |
| 119 | pc.dim('│'), |
| 120 | ]; |
| 121 | |
| 122 | const selectedCount = levels.filter((l) => l !== 'skip').length; |
| 123 | const footerChrome = [ |
| 124 | pc.dim('│'), |
| 125 | `${pc.dim('│')} ${pc.dim(`${selectedCount} package${selectedCount !== 1 ? 's' : ''} selected`)}`, |
| 126 | pc.dim('└'), |
| 127 | ]; |
| 128 | |
| 129 | // Determine viewport size: how many body lines fit in the terminal. |
| 130 | const termRows = stdout.rows || 24; |
| 131 | const chromeLines = headerChrome.length + footerChrome.length; |
| 132 | const MIN_BODY = 3; |
| 133 | const availableBody = Math.max(MIN_BODY, termRows - chromeLines - 1); |
| 134 | |
| 135 | let visibleRows: Row[]; |
| 136 | let topIndicator: string | null = null; |
| 137 | let bottomIndicator: string | null = null; |
| 138 | let stickyHeader: string | null = null; |
| 139 | |
| 140 | if (rows.length <= availableBody) { |
| 141 | visibleRows = rows; |
| 142 | scroll = 0; |
| 143 | } else { |
| 144 | // Reserve up to 2 lines for scroll indicators (one above, one below). |
| 145 | let windowSize = Math.max(MIN_BODY, availableBody - 2); |
no test coverage detected
searching dependent graphs…