Skip to content

Commit a07cb24

Browse files
takemi-ohamaclaude
andcommitted
fix(list): 非 TTY フォールバック判定に stdout.isatty() を追加
`devbase list | cat` や `devbase list > out.txt` のように stdout だけが 非 TTY のケースでも対話選択が起動してしまう問題を修正。stdin / stdout の いずれかが非 TTY なら確実に一覧表示へフォールバックするよう判定を拡張した。 stdout 非 TTY フォールバックのテストも追加。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e2d15e7 commit a07cb24

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

lib/devbase/commands/project.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ def cmd_project_list(devbase_root: Path, args) -> int:
177177

178178
# 対話選択はデフォルト ON。ただし非 TTY (パイプ / CI / リダイレクト) では
179179
# input() が EOFError になり実用にならないため、自動的に一覧表示へフォールバック。
180-
if getattr(args, "interactive", True) and sys.stdin.isatty():
180+
# stdin / stdout のいずれかが非 TTY (`devbase list | cat`, `> out.txt` 等) なら
181+
# 対話プロンプトが表示できない / 読めないため、確実に一覧表示へフォールバックする。
182+
if getattr(args, "interactive", True) and sys.stdin.isatty() and sys.stdout.isatty():
181183
return _interactive_select_and_up(rows)
182184

183185
_print_table(rows)

tests/cli/test_project_list.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,32 @@ def test_cmd_project_list_non_tty_falls_back_to_table(tmp_path, monkeypatch, cap
246246
assert "alpha-proj" in out
247247

248248

249+
def test_cmd_project_list_stdout_non_tty_falls_back_to_table(tmp_path, monkeypatch, capsys):
250+
"""stdin が TTY でも stdout が非 TTY (`devbase list | cat` / `> out.txt`) なら
251+
対話起動せず一覧表示へフォールバックする。"""
252+
from devbase.commands import project as project_mod
253+
from devbase.commands import status as status_mod
254+
from devbase.commands import container as container_mod
255+
256+
_make_plugin_project(tmp_path, "repos/o--r/alpha", "alpha-proj")
257+
_link_project(tmp_path, "alpha-proj", "repos/o--r/alpha", "alpha-proj")
258+
monkeypatch.setattr(status_mod, "_container_status_for",
259+
lambda entry: {"name": entry.name, "status": "stopped", "count": 0})
260+
monkeypatch.setattr(project_mod.sys.stdin, "isatty", lambda: True)
261+
monkeypatch.setattr(project_mod.sys.stdout, "isatty", lambda: False)
262+
263+
called = []
264+
monkeypatch.setattr(container_mod, "cmd_project", lambda args: called.append(1) or 0)
265+
266+
args = types.SimpleNamespace(interactive=True)
267+
rc = project_mod.cmd_project_list(tmp_path, args)
268+
out = capsys.readouterr().out
269+
270+
assert rc == 0
271+
assert called == [], "stdout 非 TTY では対話起動しない"
272+
assert "alpha-proj" in out
273+
274+
249275
# ---------------------------------------------------------------------------
250276
# cmd_project_list: --interactive
251277
# ---------------------------------------------------------------------------
@@ -263,6 +289,7 @@ def test_cmd_project_list_interactive_selects_and_ups(tmp_path, monkeypatch):
263289

264290
# 対話選択は TTY 環境でのみ起動するため isatty を True に固定する。
265291
monkeypatch.setattr(project_mod.sys.stdin, "isatty", lambda: True)
292+
monkeypatch.setattr(project_mod.sys.stdout, "isatty", lambda: True)
266293
# 番号 "2" を選択 (sorted: alpha-proj=1, beta-proj=2)
267294
monkeypatch.setattr("builtins.input", lambda *a, **k: "2")
268295

@@ -288,6 +315,7 @@ def test_cmd_project_list_interactive_empty_input_aborts(tmp_path, monkeypatch):
288315
_link_project(tmp_path, "alpha-proj", "repos/o--r/alpha", "alpha-proj")
289316
monkeypatch.setattr(status_mod, "_container_status_for", lambda entry: None)
290317
monkeypatch.setattr(project_mod.sys.stdin, "isatty", lambda: True)
318+
monkeypatch.setattr(project_mod.sys.stdout, "isatty", lambda: True)
291319
monkeypatch.setattr("builtins.input", lambda *a, **k: "")
292320

293321
called = []
@@ -313,6 +341,7 @@ def raise_eof(*a, **k):
313341
raise EOFError
314342

315343
monkeypatch.setattr(project_mod.sys.stdin, "isatty", lambda: True)
344+
monkeypatch.setattr(project_mod.sys.stdout, "isatty", lambda: True)
316345
monkeypatch.setattr("builtins.input", raise_eof)
317346
called = []
318347
monkeypatch.setattr(container_mod, "cmd_project", lambda args: called.append(1) or 0)
@@ -337,6 +366,7 @@ def raise_interrupt(*a, **k):
337366
raise KeyboardInterrupt
338367

339368
monkeypatch.setattr(project_mod.sys.stdin, "isatty", lambda: True)
369+
monkeypatch.setattr(project_mod.sys.stdout, "isatty", lambda: True)
340370
monkeypatch.setattr("builtins.input", raise_interrupt)
341371
called = []
342372
monkeypatch.setattr(container_mod, "cmd_project", lambda args: called.append(1) or 0)
@@ -358,6 +388,7 @@ def test_cmd_project_list_interactive_out_of_range_reprompts(tmp_path, monkeypat
358388
monkeypatch.setattr(status_mod, "_container_status_for", lambda entry: None)
359389

360390
monkeypatch.setattr(project_mod.sys.stdin, "isatty", lambda: True)
391+
monkeypatch.setattr(project_mod.sys.stdout, "isatty", lambda: True)
361392
# "99" (範囲外) → "1" (有効) の順に入力 → 再入力後に up が起動する
362393
inputs = iter(["99", "1"])
363394
monkeypatch.setattr("builtins.input", lambda *a, **k: next(inputs))
@@ -382,6 +413,7 @@ def test_cmd_project_list_interactive_non_numeric_reprompts(tmp_path, monkeypatc
382413
monkeypatch.setattr(status_mod, "_container_status_for", lambda entry: None)
383414

384415
monkeypatch.setattr(project_mod.sys.stdin, "isatty", lambda: True)
416+
monkeypatch.setattr(project_mod.sys.stdout, "isatty", lambda: True)
385417
# "abc" (数値以外) → "1" (有効)
386418
inputs = iter(["abc", "1"])
387419
monkeypatch.setattr("builtins.input", lambda *a, **k: next(inputs))

0 commit comments

Comments
 (0)