renderOptions.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import { openSync } from 'fs'
  2. import { ReadStream } from 'tty'
  3. import type { RenderOptions } from '../ink.js'
  4. import { isEnvTruthy } from './envUtils.js'
  5. import { logError } from './log.js'
  6. // Cached stdin override - computed once per process
  7. let cachedStdinOverride: ReadStream | undefined | null = null
  8. /**
  9. * Gets a ReadStream for /dev/tty when stdin is piped.
  10. * This allows interactive Ink rendering even when stdin is a pipe.
  11. * Result is cached for the lifetime of the process.
  12. */
  13. function getStdinOverride(): ReadStream | undefined {
  14. // Return cached result if already computed
  15. if (cachedStdinOverride !== null) {
  16. return cachedStdinOverride
  17. }
  18. // No override needed if stdin is already a TTY
  19. if (process.stdin.isTTY) {
  20. cachedStdinOverride = undefined
  21. return undefined
  22. }
  23. // Skip in CI environments
  24. if (isEnvTruthy(process.env.CI)) {
  25. cachedStdinOverride = undefined
  26. return undefined
  27. }
  28. // Skip if running MCP (input hijacking breaks MCP)
  29. if (process.argv.includes('mcp')) {
  30. cachedStdinOverride = undefined
  31. return undefined
  32. }
  33. // No /dev/tty on Windows
  34. if (process.platform === 'win32') {
  35. cachedStdinOverride = undefined
  36. return undefined
  37. }
  38. // Try to open /dev/tty as an alternative input source
  39. try {
  40. const ttyFd = openSync('/dev/tty', 'r')
  41. const ttyStream = new ReadStream(ttyFd)
  42. // Explicitly set isTTY to true since we know /dev/tty is a TTY.
  43. // This is needed because some runtimes (like Bun's compiled binaries)
  44. // may not correctly detect isTTY on ReadStream created from a file descriptor.
  45. ttyStream.isTTY = true
  46. cachedStdinOverride = ttyStream
  47. return cachedStdinOverride
  48. } catch (err) {
  49. logError(err as Error)
  50. cachedStdinOverride = undefined
  51. return undefined
  52. }
  53. }
  54. /**
  55. * Returns base render options for Ink, including stdin override when needed.
  56. * Use this for all render() calls to ensure piped input works correctly.
  57. *
  58. * @param exitOnCtrlC - Whether to exit on Ctrl+C (usually false for dialogs)
  59. */
  60. export function getBaseRenderOptions(
  61. exitOnCtrlC: boolean = false,
  62. ): RenderOptions {
  63. const stdin = getStdinOverride()
  64. const options: RenderOptions = { exitOnCtrlC }
  65. if (stdin) {
  66. options.stdin = stdin
  67. }
  68. return options
  69. }