TTY非接続時のプログレス表示をスキップする分岐を追加
CLIがTTYに接続されていない環境(CIやパイプ経由の実行)では、インタラクティブなプログレス表示をスキップしてタスクを直接実行するよう変更されました。あわせて、deploy・updateコマンドの呼び出しを新しい関数 runWithProgress に一本化しています。
背景
これまでの実装では、cliProgress.Run() が常に tea.NewProgram を使ったTUI(テキストUI)レンダリングを起動していました。TTYが存在しないCI環境やリダイレクト実行では、bubbletea のプログラムが正常に動作せず、プログレス表示が壊れるか予期しない動作を引き起こす可能性がありました。
この問題を解消するため、TTYの有無を isTerminal() で判定し、非TTY環境ではプログレス表示なしでタスクを直接実行するパスが追加されました。
技術的な変更
internal/command/cli_progress.go に runWithProgress 関数が新設され、TTY判定を含むプログレス実行ロジックが一元管理されるようになりました。
変更前:
func (m *cliProgress) Run() error {
_, err := tea.NewProgram(m).Run()
if err != nil {
return err
}
if m.err != nil {
fmt.Printf("%s: %s\n", m.label, lipgloss.NewStyle().Foreground(lipgloss.Red).Render("failed"))
} else {
fmt.Printf("%s: %s\n", m.label, lipgloss.NewStyle().Foreground(lipgloss.Green).Render("done"))
}
// ...
}
変更後:
func runWithProgress(label string, task func(docker.DeployProgressCallback) error) error {
var err error
if isTerminal() {
p := newCLIProgress(label, task)
if _, runErr := tea.NewProgram(p).Run(); runErr != nil {
return runErr
}
err = p.err
} else {
err = task(func(docker.DeployProgress) {})
}
if err != nil {
fmt.Printf("%s: %s\n", label, lipgloss.NewStyle().Foreground(lipgloss.Red).Render("failed"))
} else {
fmt.Printf("%s: %s\n", label, lipgloss.NewStyle().Foreground(lipgloss.Green).Render("done"))
}
// ...
}
TTY非接続時は task(func(docker.DeployProgress) {}) を直接呼び出します。コールバックには空の関数を渡すことで、プログレス通知を受け取りつつ何もしない設計になっています。成功・失敗のサマリー行(done / failed)はTTYの有無にかかわらず出力されます。
deploy.go と update.go では、newCLIProgress + p.Run() の2ステップが runWithProgress の1呼び出しに置き換えられています。
// 変更前
p := newCLIProgress("Deploying "+host, func(progress docker.DeployProgressCallback) error {
// ...
})
return p.Run()
// 変更後
return runWithProgress("Deploying "+host, func(progress docker.DeployProgressCallback) error {
// ...
})
設計判断
cliProgress のメソッドとしてではなく、独立した関数 として runWithProgress が実装されている点が注目されます。TTY判定の結果によって cliProgress オブジェクト自体を生成しないケースがあるため、インスタンスメソッドではなくパッケージレベルの関数として切り出すのが自然な判断です。
非TTY時のコールバックに func(docker.DeployProgress) {} を渡すことで、task 関数のシグネチャを変えずにプログレス通知を無視できます。これにより、task の実装側はTTYの有無を意識する必要がなく、呼び出し側の関心事として分離されています。
まとめ
TTY判定ロジックを runWithProgress に集約したことで、呼び出し元の deploy・update コマンドはプログレス表示の制御を意識せずにタスクを渡せるようになりました。CI環境などのTTY非接続時でも安定して動作し、コマンドの呼び出しインターフェースもシンプルになっています。