TTY非接続時のプログレス表示をスキップする分岐を追加

basecamp/once

CLIがTTYに接続されていない環境(CIやパイプ経由の実行)では、インタラクティブなプログレス表示をスキップしてタスクを直接実行するよう変更されました。あわせて、deployupdateコマンドの呼び出しを新しい関数 runWithProgress に一本化しています。

背景

これまでの実装では、cliProgress.Run() が常に tea.NewProgram を使ったTUI(テキストUI)レンダリングを起動していました。TTYが存在しないCI環境やリダイレクト実行では、bubbletea のプログラムが正常に動作せず、プログレス表示が壊れるか予期しない動作を引き起こす可能性がありました。

この問題を解消するため、TTYの有無を isTerminal() で判定し、非TTY環境ではプログレス表示なしでタスクを直接実行するパスが追加されました。

技術的な変更

internal/command/cli_progress.gorunWithProgress 関数が新設され、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.goupdate.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 に集約したことで、呼び出し元の deployupdate コマンドはプログレス表示の制御を意識せずにタスクを渡せるようになりました。CI環境などのTTY非接続時でも安定して動作し、コマンドの呼び出しインターフェースもシンプルになっています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
4c3269ef

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)の3部構成が明確に守られています。各セクションの役割が明確で、読者が記事の全体像を把握しやすい構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きのコードブロック記法(```go:path/to/file.go)やPR番号のリンク記法([PR #37](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

TTY、CI、Goのコールバックといった専門用語が適切に使用されており、専門知識を持つエンジニアという対象読者に適した技術レベルで記述されています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論・各論で構成され、各段落はトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に読みやすいです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されている変更前後のコードは、提供されたDiff情報と完全に一致しており、正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

TTY, CI, TUI, コールバックなどの技術用語が、文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「isTerminal()によるTTY判定」や「空の関数を渡すコールバック」といった技術的な変更点の説明は、Diffの内容と一致しており、論理的で正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の主張はすべてPRのタイトルやDiffのコード内容に基づいており、根拠のない推測や捏造(ハルシネーション)は見られません。「設計判断」セクションはコードから読み取れる意図を解説しており、付加価値が高いです。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#37)、関数名(runWithProgress)、ファイルパスなどがすべて正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「TTY非接続時のプログレス表示をスキップする分岐を追加」は、PRのタイトル「Don't show progress when CLI has no TTY」の内容を的確に要約しており、整合性が取れています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PRで言及されていないバージョン情報やリリース予定などの外部知識は含まれておらず、提供された情報に忠実です。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「これまでの実装では」といった時間表現が正確に使われており、変更の前後関係を正しく伝えています。