Project

pty_compat

0.0
The project is in a healthy, maintained state
Make Ruby's PTY work on all platforms
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 2.7
 Project Readme

pty_compat

Make Ruby's PTY work on all platforms, including Windows

License Gem Version CI Stars

What is this?

Ruby's built-in PTY module is not available on Windows. pty_compat transparently replaces it with an equivalent implementation using node-pty, so your code works everywhere without changes.

A single require 'pty_compat' patches PTY.spawn to fall back to a Node.js bridge when the native module is unavailable. No migration, no conditional logic, no platform checks.

Table of contents

  • What is this?
  • Quick Start
    • Installation
    • Usage
  • Requirements
  • Features
  • Public API
  • Documentation
  • How it works
  • Development
  • Contributing
  • License

Quick Start

Installation

bundle add pty_compat

If you're not using Bundler:

gem install pty_compat

On Windows, you also need node-pty:

npm install node-pty

Tip

If your project does not use Node.js, you can install node-pty locally. The bridge script resolves it from the current working directory.

Usage

require 'pty_compat'

# Non-block form
reader, writer, pid = PTY.spawn('ping', '-c', '3', 'example.com')
writer.puts('input')
reader.each_line { |line| puts line }
Process.wait(pid)

# Block form
PTY.spawn('ping', '-c', '3', 'example.com') do |reader, writer, pid|
  writer.puts('input')
  reader.each_line { |line| puts line }
end

# Retrieve the exit status portably
status = Process::Status.wait

Requirements

  • Ruby >= 3.1
  • Node.js and node-pty (only required on platforms without native PTY support, typically Windows)

Features

  • Zero-config drop-in. A single require replaces PTY.spawn on Windows — no configuration, no platform checks, no conditional logic.
  • Portable exit status. Use Process::Status.wait to retrieve the exit code on any platform.
  • Non-block & block forms. Supports both PTY.spawn(command, args...) -> [reader, writer, pid] and PTY.spawn(command, args...) { |reader, writer, pid| ... } forms.
  • Windows support. Leverages Microsoft's node-pty to provide a proper PTY on Windows, where Ruby's native PTY is unavailable.
  • Lightweight. The Ruby codebase is minimal, delegating the heavy lifting to a well-maintained native module.
  • Works on all platforms. Falls back to the node-pty bridge only when the native PTY module is unavailable; otherwise uses the standard library unchanged.

Public API

PTY.spawn(command, *args) -> [reader, writer, pid]

Spawns a new process attached to a pseudo-terminal.

Parameter Type Description
command String The command to execute (e.g. 'ping').
*args String... Zero or more arguments passed to the command.

Non-block form returns a three-element array:

Element Type Description
reader IO Readable IO (stdout + stderr merged).
writer IO Writable IO (stdin of the spawned process).
pid Integer Process ID of the spawned process.

PTY.spawn(command, *args) { |reader, writer, pid| block }

Block form yields reader, writer, and pid to the given block, and automatically closes the IOs after the block returns.

Process::Status.wait -> Process::Status | nil

Returns the exit status of the last spawned process.

  • On platforms with native PTY, uses the default Process::Status.wait.
  • On the fallback path, returns a Process::Status constructed from the exit code captured by the node-pty bridge.
  • Returns nil if no process has been spawned yet or if the last spawn failed.

Tip

Use Process::Status.wait for portable code that runs on both Windows and Unix.

Documentation

How it works

  1. pty_compat tries to load Ruby's standard PTY library first.
  2. On LoadError (as raised on Windows), it prepends PtyCompat::NodePty into PTY.
  3. PtyCompat::NodePty implements PTY.spawn through a Node.js bridge that uses node-pty to create a pseudo-terminal.
  4. Stdout and stderr are merged into a single readable IO, matching the behaviour of Ruby's native PTY.spawn.
┌──────────────┐     ┌──────────────────┐     ┌──────────────┐
│  Ruby code   │────▶│  PTY.spawn(...)  │────▶│  node-pty    │
│  (your app)  │     │  (patched)       │     │  bridge.js   │
└──────────────┘     └──────────────────┘     └──────┬───────┘
                                                     │
                                                     ▼
                                              ┌──────────────┐
                                              │  Command     │
                                              │  (shell,     │
                                              │   process)   │
                                              └──────────────┘

Why not a pure Ruby PTY?

Alternative approaches rely on platform-specific C extensions that are painful to compile on Windows, or expose an incomplete PTY interface. pty_compat delegates the heavy lifting to node-pty, a well-maintained native module by Microsoft that supports Windows, macOS, and Linux. This keeps the Ruby code small and the platform coverage broad.

Development

bundle install

Run the tests:

bundle exec rspec

Lint with RuboCop:

bundle exec rubocop

Contributing

Bug reports and pull requests are welcome on GitHub at Muriel-Salvan/pty_compat.

License

The gem is available as open source under the terms of the BSD-3-Clause License.


Star History Chart