PTY の起動、制御、出力取得
標準出力を通常のパイプ(pipe)へリダイレクトしただけでは、端末上で人間が対話的にコマンドを実行したときと同じ挙動にはなりません。 多くのコマンドラインツールは、標準出力が端末(TTY)に接続されているかどうかを判定し、パイプ接続を検知するとカラー出力、プログレスバー、対話型 UI を自動的に停止してしまうためです。 console2svg ではこの制約を回避するため、PTY(Pseudo-Terminal:OS が提供する擬似端末機構)を介して子プロセスを実行し、本物の端末に接続されている環境を再現します。
プラットフォームに応じた PTY バックエンドの抽象化
Section titled “プラットフォームに応じた PTY バックエンドの抽象化”PTY の生成と管理には Porta.Pty ライブラリを使用しています。
プラットフォーム固有のシステムコールを直接呼ぶのではなく、Windows の ConPTY(Pseudoconsole API)と Unix 系の PTY(controller/terminal ペア)の差異をライブラリ境界で吸収し、上位レイヤーからは共通のストリームとして扱います。
子プロセスを生成する際は、指定された幅(列数)と高さ(行数)を設定したうえで、環境変数 COLUMNS と LINES にも同一の値を注入します。
シェルの呼び出しは、Windows では cmd.exe /c、Unix 系では /bin/sh -c を介して行われます。
Windows における cmd.exe のコマンドライン引数は、C ランタイムの標準的なエスケープ規則とは異なるため、Windows 固有の専用クォート処理を通して安全にコマンドライン文字列を組み立てます。
既定の動作では、親プロセスから引き継いだ環境変数のうち CI や TF_BUILD などの環境変数を子プロセスから自動的に除外します。
近年の開発ツールやテストフレームワークは、たとえ PTY 上で動作していても、これらの環境変数が存在すると無条件で非対話・モノクロ表示モードへ倒してしまうためです。
親環境をそのまま渡したい場合は、--no-delete-envs オプションでこの自動削除を無効化できます。
文字境界を崩さないストリーム読み取り
Section titled “文字境界を崩さないストリーム読み取り”PTY からの出力読み取りループでは、1 つのバイトバッファ、1 つの文字バッファ、そして状態を保持するステートフルな UTF-8 デコーダーを使い続けます。 OS の read 境界によってマルチバイト文字(日本語の全角文字など)の途中でバイト列が分断された場合でも、未完了のバイト状態を次回の read 処理へ確実に引き継ぎます。
端末画面へリアルタイムに出力を中継(フォワーディング)する際は、受信した生のバイト列をそのまま標準出力へ流します。 途中で文字列へデコードして再エンコードする無駄を排し、VT シーケンス(カーソル移動や文字色を指定するエスケープシーケンス)の構造が変化するのを防ぎます。
録画データとして記録する文字列は、経過時間とともに RecordingSession へ追加します。
この段階では出力を改行単位で分割しません。
キャリッジリターン(\r)、カーソル移動、行消去、画面切り替えといった制御文字も、後段の端末エミュレーターが画面状態を再現するために不可欠な入力データだからです。
近接する出力イベントのコアレッシング
Section titled “近接する出力イベントのコアレッシング”1 回の画面描画であっても、子プロセスからの出力は細かな PTY read に分断されて届くことが珍しくありません。 read が発生するたびに個別の録画イベントを生成してしまうと、後段の ANSI パーサーが意味のない描画途中の状態までパースすることになり、不要な負荷が生じます。
そこで console2svg は、時間的に近接した複数の出力を 出力コアレッシング によって 1 つのイベントへ統合します。 まとめる時間幅は、動画の 1 フレーム時間の 4 分の 1 を基準とし、2 ミリ秒から 20 ミリ秒の範囲で自動調整します。 統合されたイベントには、そのバッチの最後のチャンクが届いた時刻を付与して記録します。
対話入力の透過的な転送
Section titled “対話入力の透過的な転送”対話キャプチャ(interactive)の実行中は、ホスト側のキー入力を生のまま受け取る Raw モードに設定します。
矢印キーや Ctrl キーなどの操作がホスト側シェルに横取りされるのを防ぎ、エスケープシーケンスとしてそのまま子プロセスへ転送するためです。
Unix 環境で標準入力がリダイレクトされている場合でも、可能であれば /dev/tty を直接開いてキー入力を継続します。
Windows のコンソール入力は、現在の入力コードページで文字列へ変換してから UTF-8 で PTY へ転送します。リプレイデータも UTF-8 化した入力から生成し、矢印キーなどの VT シーケンスは ASCII のまま保ちます。 入力読み取りの末尾でエスケープシーケンスが途切れていた場合は、残りのバイトを次回読み取りへ持ち越し、不完全な断片を誤ったキー入力として記録しないように保護します。
プロセス終了後の残留出力回収
Section titled “プロセス終了後の残留出力回収”子プロセスのプロセス終了シグナルと、PTY バッファ内の全バイト読み取り完了は、必ずしも同時に起きるとは限りません。 プロセスが終了した直後であっても、OS のカーネルバッファ内に未処理の出力が残っている場合があります。
そのため、プロセス終了後も最大 500 ミリ秒間は出力リーダーを継続して稼働させ、末尾の残留データを完全に回収します。 また、プロセスのクリーンアップ時には最大 1 秒のタイムアウトを設け、バックエンドの終了処理が何らかの原因でスタックした場合でも、CLI 全体の処理が無期限に停止しないよう安全弁を設けています。
PTY 起動失敗時のフォールバック
Section titled “PTY 起動失敗時のフォールバック”セキュリティ制限や特殊なコンテナ環境など、環境によってはネイティブの PTY バックエンドが正常に起動しない場合があります。 また、PTY プロセスが立ち上がったにもかかわらず一定時間出力を全く生成しないハング状態に陥るケースもあります。
console2svg では、起動時の無応答を検知した場合に最大 3 回まで PTY の再起動を試行します。 それでも PTY を確立できない場合は、通常の標準入出力をリダイレクトする標準プロセス実行へとフォールバックします。 フォールバック経路では TTY に依存するリッチな表示(プログレスバーや対話画面)の再現性は制限されますが、非対話コマンドの実行結果を記録不能にしてしまう事態を回避します。