78 lines
2.2 KiB
TypeScript
78 lines
2.2 KiB
TypeScript
import { openSync } from 'fs'
|
|
import { ReadStream } from 'tty'
|
|
import type { RenderOptions } from '../ink.js'
|
|
import { isEnvTruthy } from './envUtils.js'
|
|
import { logError } from './log.js'
|
|
|
|
// Cached stdin override - computed once per process
|
|
let cachedStdinOverride: ReadStream | undefined | null = null
|
|
|
|
/**
|
|
* Gets a ReadStream for /dev/tty when stdin is piped.
|
|
* This allows interactive Ink rendering even when stdin is a pipe.
|
|
* Result is cached for the lifetime of the process.
|
|
*/
|
|
function getStdinOverride(): ReadStream | undefined {
|
|
// Return cached result if already computed
|
|
if (cachedStdinOverride !== null) {
|
|
return cachedStdinOverride
|
|
}
|
|
|
|
// No override needed if stdin is already a TTY
|
|
if (process.stdin.isTTY) {
|
|
cachedStdinOverride = undefined
|
|
return undefined
|
|
}
|
|
|
|
// Skip in CI environments
|
|
if (isEnvTruthy(process.env.CI)) {
|
|
cachedStdinOverride = undefined
|
|
return undefined
|
|
}
|
|
|
|
// Skip if running MCP (input hijacking breaks MCP)
|
|
if (process.argv.includes('mcp')) {
|
|
cachedStdinOverride = undefined
|
|
return undefined
|
|
}
|
|
|
|
// No /dev/tty on Windows
|
|
if (process.platform === 'win32') {
|
|
cachedStdinOverride = undefined
|
|
return undefined
|
|
}
|
|
|
|
// Try to open /dev/tty as an alternative input source
|
|
try {
|
|
const ttyFd = openSync('/dev/tty', 'r')
|
|
const ttyStream = new ReadStream(ttyFd)
|
|
// Explicitly set isTTY to true since we know /dev/tty is a TTY.
|
|
// This is needed because some runtimes (like Bun's compiled binaries)
|
|
// may not correctly detect isTTY on ReadStream created from a file descriptor.
|
|
ttyStream.isTTY = true
|
|
cachedStdinOverride = ttyStream
|
|
return cachedStdinOverride
|
|
} catch (err) {
|
|
logError(err as Error)
|
|
cachedStdinOverride = undefined
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns base render options for Ink, including stdin override when needed.
|
|
* Use this for all render() calls to ensure piped input works correctly.
|
|
*
|
|
* @param exitOnCtrlC - Whether to exit on Ctrl+C (usually false for dialogs)
|
|
*/
|
|
export function getBaseRenderOptions(
|
|
exitOnCtrlC: boolean = false,
|
|
): RenderOptions {
|
|
const stdin = getStdinOverride()
|
|
const options: RenderOptions = { exitOnCtrlC }
|
|
if (stdin) {
|
|
options.stdin = stdin
|
|
}
|
|
return options
|
|
}
|