Skip to content

Commit 3ccfce0

Browse files
takemi-ohamaclaude
andcommitted
fix(tui): 破壊的操作以外の y/N 確認プロンプトを廃止
devbase list の TUI で非破壊操作にも y/N 確認が出て操作が冗長だったため、 明らかに破壊的な操作 (uninstall / repo remove / env delete / snapshot restore / snapshot delete) 以外の確認プロンプトをすべて廃止し、CLI 既定値で 即実行するようにする。 - plugin list: --available の y/N を廃止し、独立したメニュー項目 「利用可能一覧 (list --available)」として提供 (機能は維持) - plugin install: --link / --all の y/N を廃止 (False 固定、CLI で指定可) - project down: 確認を廃止 (volume 保持・up で復旧可能なため) - project ps: --all の y/N を廃止 (False 固定) - project logs: --follow の y/N を廃止 (tail 行数入力のみ) - env init: --reset の y/N を廃止 (セットアップ済みなら案内表示で安全終了) - env list: --reveal / --keys の y/N を廃止 (伏せ字・通常表示) - snapshot create: --full の y/N を廃止 (増分バックアップ) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 7aff48c commit 3ccfce0

8 files changed

Lines changed: 81 additions & 173 deletions

File tree

lib/devbase/tui/actions_env.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,8 @@ def _select_scoped_project(devbase_root: Path, message: str, choices):
211211
# 各操作の引数収集 + dispatch (plan 2.3 契約)
212212
# ---------------------------------------------------------------------------
213213

214-
def _op_init(devbase_root: Path):
215-
# 既存設定がある場合は --reset でやり直し (既存はバックアップされる)。
216-
reset = flow.need(menu.confirm(
217-
"既存の設定をバックアップしてやり直しますか (--reset)?", default=False))
218-
return _dispatch(devbase_root, "init", reset=reset)
219-
220-
221214
def _op_list(devbase_root: Path):
222-
"""``env list``: 表示範囲 + 表示オプションを収集して一覧表示する
215+
"""``env list``: 表示範囲を収集して一覧表示する
223216
224217
ハンドラ (``cmd_env_list``) は CWD (PWD) が projects/ 配下のときだけ
225218
プロジェクト .env を表示するため、プロジェクトを含む表示範囲
@@ -233,13 +226,12 @@ def _op_list(devbase_root: Path):
233226
[("グローバル + プロジェクト", "both"),
234227
("グローバルのみ (--global)", "global"),
235228
("プロジェクトのみ (--project)", "project")])
236-
reveal = flow.need(menu.confirm(
237-
"機密値を伏せ字にせず表示しますか (--reveal)?", default=False))
238-
keys_only = flow.need(menu.confirm("キー名のみ表示しますか (--keys)?", default=False))
239229

230+
# --reveal / --keys は CLI 既定 (False = 機密値は伏せ字・通常表示) で実行する
231+
# (非破壊操作の確認プロンプト廃止)。必要な場合は CLI を使う想定。
240232
attrs = {"global_only": scope == "global",
241233
"project_only": scope == "project",
242-
"reveal": reveal, "keys_only": keys_only}
234+
"reveal": False, "keys_only": False}
243235
if name is None:
244236
return _dispatch(devbase_root, "list", **attrs)
245237
return _run_in_project(devbase_root, name,
@@ -321,9 +313,11 @@ def _op_import(devbase_root: Path):
321313
# sync は引数なしで即実行 (ソースファイルから認証情報を再同期する)。
322314
# edit も引数なし。$DEVBASE_ROOT/.env を $EDITOR で開くグローバル操作のため
323315
# chdir しない (plan 3.3 は CWD スコープとするが実装を正とする)。
316+
# init は --reset なし (CLI 既定) で即実行。セットアップ済みなら
317+
# cmd_env_init が案内を出して安全に終了し、やり直しは CLI --reset を使う。
324318
"sync": lambda root: _dispatch(root, "sync"),
325319
"edit": lambda root: _dispatch(root, "edit"),
326-
"init": _op_init,
320+
"init": lambda root: _dispatch(root, "init", reset=False),
327321
"list": _op_list,
328322
"set": _op_set,
329323
"get": _op_get,

lib/devbase/tui/actions_plugin.py

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727

2828
# plugin サブコマンド (表示順 = ハイライト既定順)。閲覧系の list を先頭に置き、
2929
# Enter 連打で安全な一覧表示へ到達できるようにする。value は cmd_plugin の subcommand 名
30-
# (repo のみサブ階層メニューへの分岐)。
30+
# (repo のみサブ階層メニューへの分岐)。--available は y/N で聞かず独立した
31+
# メニュー項目にする (非破壊操作の確認プロンプト廃止)。
3132
_PLUGIN_OPS: list[tuple[str, str]] = [
32-
("一覧表示 (list)", "list"),
33+
("導入済み一覧 (list)", "list"),
34+
("利用可能一覧 (list --available)", "list-available"),
3335
("インストール (install)", "install"),
3436
("アンインストール (uninstall)", "uninstall"),
3537
("更新 (update)", "update"),
@@ -149,22 +151,13 @@ def _select_repo_operation():
149151
# 各操作の引数収集 + dispatch (plan 2.3 契約)
150152
# ---------------------------------------------------------------------------
151153

152-
def _op_list(devbase_root: Path):
153-
# --available: 導入済み一覧の代わりに未導入の利用可能 plugin を表示する。
154-
available = flow.need(menu.confirm(
155-
"未導入の利用可能 plugin を表示しますか (--available)?", default=False))
156-
return _dispatch(devbase_root, "list", available=available)
157-
158-
159154
def _op_install(devbase_root: Path):
155+
# --link / --all は CLI 既定 (False) で実行する。指定が必要な場合は CLI
156+
# (`plugin install --link/--all`) を使う想定 (非破壊操作の確認プロンプト廃止)。
160157
source = flow.need(menu.text(
161158
"インストールする plugin の source (名前 / URL / パス)", allow_empty=False))
162-
link = flow.need(menu.confirm(
163-
"symlink としてインストールしますか (--link)?", default=False))
164-
install_all = flow.need(menu.confirm(
165-
"リポジトリ内の全 plugin をインストールしますか (--all)?", default=False))
166159
return _dispatch(devbase_root, "install",
167-
source=source, link=link, install_all=install_all)
160+
source=source, link=False, install_all=False)
168161

169162

170163
def _op_uninstall(devbase_root: Path):
@@ -188,7 +181,9 @@ def _op_info(devbase_root: Path):
188181

189182

190183
_OP_HANDLERS = {
191-
"list": _op_list,
184+
# list 系は引数収集なしで即実行 (--available はメニュー項目で分岐)。
185+
"list": lambda root: _dispatch(root, "list", available=False),
186+
"list-available": lambda root: _dispatch(root, "list", available=True),
192187
"install": _op_install,
193188
"uninstall": _op_uninstall,
194189
"update": _op_update,

lib/devbase/tui/actions_project.py

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
PR2 で running 操作サブメニューを **up/down/login/ps/logs/scale/build/rebuild の全操作**
99
へ拡張した。login/ps/logs/scale は running 中コンテナを対象とするため running 行限定、
1010
stopped/unknown は従来どおり直接 up (PR1 非回帰)。引数を要する操作は ``tui.menu`` の
11-
収集ヘルパで CLI と同じ属性値を集め、破壊的な down は実行前に確認する
12-
(plan 2.3 契約表 / 3.4 破壊的操作確認)
11+
収集ヘルパで CLI と同じ属性値を集める (plan 2.3 契約表)。down はデータを失わない
12+
(volume 保持) ためメニュー選択を意思表示とみなし、確認プロンプトは出さない
1313
1414
プロジェクト一覧の表示・選択は ``tui.app`` (トップ画面) が担い、本モジュールは
1515
選択された 1 行の処理 (``handle_row``) と questionary 不在時のフォールバックを提供する。
@@ -93,11 +93,6 @@ def _select_build_image(devbase_root: Path):
9393
# 各操作の引数収集 + dispatch (引数を要する操作のみ。up/rebuild は即実行)
9494
# ---------------------------------------------------------------------------
9595

96-
def _op_down(devbase_root: Path, name: str):
97-
flow.confirm_or_back(f"'{name}' のコンテナを停止しますか?")
98-
return dispatch_lifecycle("down", name)
99-
100-
10196
def _op_login(devbase_root: Path, name: str):
10297
# menu.text は空入力 (既定値を消して確定) で "" を返し、wrapper で --index=
10398
# と展開されてコマンドが失敗する。menu.integer なら空入力は default=1 を返し、
@@ -106,16 +101,11 @@ def _op_login(devbase_root: Path, name: str):
106101
return dispatch_lifecycle("login", name, index=str(index))
107102

108103

109-
def _op_ps(devbase_root: Path, name: str):
110-
all_c = flow.need(menu.confirm(
111-
"停止中も含め全コンテナを表示しますか (--all)?", default=False))
112-
return dispatch_lifecycle("ps", name, all=all_c)
113-
114-
115104
def _op_logs(devbase_root: Path, name: str):
116-
follow = flow.need(menu.confirm("ログを追従表示しますか (--follow)?", default=False))
105+
# --follow は CLI 既定 (False) で実行する (非破壊操作の確認プロンプト廃止)。
106+
# 追従が必要な場合は CLI (`project logs --follow`) を使う想定。
117107
tail = flow.need_optional(_optional_int("末尾何行を表示しますか (空で全件)"))
118-
return dispatch_lifecycle("logs", name, follow=follow, tail=tail)
108+
return dispatch_lifecycle("logs", name, follow=False, tail=tail)
119109

120110

121111
def _op_scale(devbase_root: Path, name: str):
@@ -129,13 +119,15 @@ def _op_build(devbase_root: Path, name: str):
129119

130120

131121
_OP_HANDLERS = {
132-
# up/rebuild は引数なしで即実行。up は scale 属性を参照する (常に None。
133-
# 他コマンドは無視する)。
122+
# up/down/rebuild/ps は引数なしで即実行。up/rebuild は scale 属性を参照する
123+
# (常に None。他コマンドは無視する)。down はデータを失わない (volume 保持・
124+
# up で復旧可能) ためメニュー選択を意思表示とみなし、確認プロンプトを出さない。
125+
# ps の --all は CLI 既定 (False) に揃える。
134126
"up": lambda root, name: dispatch_lifecycle("up", name, scale=None),
135127
"rebuild": lambda root, name: dispatch_lifecycle("rebuild", name, scale=None),
136-
"down": _op_down,
128+
"down": lambda root, name: dispatch_lifecycle("down", name),
137129
"login": _op_login,
138-
"ps": _op_ps,
130+
"ps": lambda root, name: dispatch_lifecycle("ps", name, all=False),
139131
"logs": _op_logs,
140132
"scale": _op_scale,
141133
"build": _op_build,

lib/devbase/tui/actions_snapshot.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,13 @@ def _optional_point(message: str):
106106
# ---------------------------------------------------------------------------
107107

108108
def _op_create(devbase_root: Path):
109+
# --full は CLI 既定 (False = 増分) で実行する (非破壊操作の確認プロンプト
110+
# 廃止)。フルバックアップ強制は CLI (`snapshot create --full`) を使う想定。
109111
name = flow.need(menu.text("スナップショット名 (空でタイムスタンプ自動命名)",
110112
allow_empty=True))
111-
full = flow.need(menu.confirm("フルバックアップを強制しますか (--full)?",
112-
default=False))
113113
# 空入力は CLI の --name 省略と同じ None (自動命名) に正規化する。
114114
return dispatch_group(cmd_snapshot, devbase_root, "create",
115-
name=name or None, full=full)
115+
name=name or None, full=False)
116116

117117

118118
def _op_restore(devbase_root: Path):

tests/cli/tui/test_actions_env.py

Lines changed: 16 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -166,30 +166,21 @@ def test_run_operation_edit_is_global_no_project_select(monkeypatch, tmp_path):
166166
assert captured["cwd"] == before, "edit は chdir しない"
167167

168168

169-
@pytest.mark.parametrize("reset", [True, False])
170-
def test_run_operation_init_collects_reset(monkeypatch, tmp_path, reset):
171-
"""init は confirm の結果を --reset として渡す (plan 2.3: reset 既定 False)。"""
169+
def test_run_operation_init_runs_without_confirm(monkeypatch, tmp_path):
170+
"""init は確認プロンプトなしで reset=False (CLI 既定) のまま即実行する。
171+
172+
セットアップ済みの環境では cmd_env_init が案内を出して安全に終了する。
173+
やり直しは CLI (`env init --reset`) を使う想定。
174+
"""
172175
captured = _capture_dispatch(monkeypatch)
173-
monkeypatch.setattr(menu, "confirm", lambda *a, **k: reset)
176+
monkeypatch.setattr(menu, "confirm",
177+
lambda *a, **k: pytest.fail("init で確認を求めない"))
174178
assert actions_env._run_operation(tmp_path, "init") == 0
175-
assert captured["attrs"] == {"subcommand": "init", "reset": reset}
176-
177-
178-
@pytest.mark.parametrize("confirm_ret", ["BACK", None])
179-
def test_run_operation_init_cancel(monkeypatch, tmp_path, confirm_ret):
180-
"""init の confirm で Esc は再表示 (_ARG_CANCEL)、Ctrl-C は全体中止 (None)。"""
181-
from devbase.commands import env as env_mod
182-
called = []
183-
monkeypatch.setattr(env_mod, "cmd_env", lambda root, args: called.append(1) or 0)
184-
ret = menu.MENU_BACK if confirm_ret == "BACK" else None
185-
monkeypatch.setattr(menu, "confirm", lambda *a, **k: ret)
186-
expected = actions_env._ARG_CANCEL if confirm_ret == "BACK" else None
187-
assert actions_env._run_operation(tmp_path, "init") is expected
188-
assert called == []
179+
assert captured["attrs"] == {"subcommand": "init", "reset": False}
189180

190181

191182
# ---------------------------------------------------------------------------
192-
# _run_operation: list (表示範囲 + reveal/keys)
183+
# _run_operation: list (表示範囲のみ収集。reveal/keys は CLI 既定の False)
193184
# ---------------------------------------------------------------------------
194185

195186
def test_run_operation_list_global_scope_no_chdir(monkeypatch, tmp_path):
@@ -198,15 +189,15 @@ def test_run_operation_list_global_scope_no_chdir(monkeypatch, tmp_path):
198189
monkeypatch.setattr(menu, "select", lambda *a, **k: "global")
199190
monkeypatch.setattr(actions_env, "_select_project",
200191
lambda root: pytest.fail("global でプロジェクト選択してはいけない"))
201-
confirms = iter([True, False]) # reveal=True, keys_only=False
202-
monkeypatch.setattr(menu, "confirm", lambda *a, **k: next(confirms))
192+
monkeypatch.setattr(menu, "confirm",
193+
lambda *a, **k: pytest.fail("list で確認を求めない"))
203194

204195
before = os.getcwd()
205196
assert actions_env._run_operation(tmp_path, "list") == 0
206197
assert captured["attrs"] == {
207198
"subcommand": "list",
208199
"global_only": True, "project_only": False,
209-
"reveal": True, "keys_only": False,
200+
"reveal": False, "keys_only": False,
210201
}
211202
assert captured["cwd"] == before, "global スコープは chdir しない"
212203

@@ -223,16 +214,14 @@ def test_run_operation_list_both_scope_chdirs_and_restores(monkeypatch, tmp_path
223214
target.mkdir(parents=True)
224215
monkeypatch.setattr(menu, "select", lambda *a, **k: "both")
225216
monkeypatch.setattr(actions_env, "_select_project", lambda root: "carmo")
226-
confirms = iter([True, False]) # reveal=True, keys_only=False
227-
monkeypatch.setattr(menu, "confirm", lambda *a, **k: next(confirms))
228217
monkeypatch.setenv("PWD", str(tmp_path))
229218

230219
before = os.getcwd()
231220
assert actions_env._run_operation(tmp_path, "list") == 0
232221
assert captured["attrs"] == {
233222
"subcommand": "list",
234223
"global_only": False, "project_only": False,
235-
"reveal": True, "keys_only": False,
224+
"reveal": False, "keys_only": False,
236225
}
237226
# ハンドラ実行中は projects/carmo に居る (グローバル + プロジェクト両方が出る)
238227
assert captured["cwd"] == str(target)
@@ -253,16 +242,14 @@ def test_run_operation_list_project_chdirs_and_restores(monkeypatch, tmp_path):
253242
target.mkdir(parents=True)
254243
monkeypatch.setattr(menu, "select", lambda *a, **k: "project")
255244
monkeypatch.setattr(actions_env, "_select_project", lambda root: "carmo")
256-
confirms = iter([False, True]) # reveal=False, keys_only=True
257-
monkeypatch.setattr(menu, "confirm", lambda *a, **k: next(confirms))
258245
monkeypatch.setenv("PWD", str(tmp_path))
259246

260247
before = os.getcwd()
261248
assert actions_env._run_operation(tmp_path, "list") == 0
262249
assert captured["attrs"] == {
263250
"subcommand": "list",
264251
"global_only": False, "project_only": True,
265-
"reveal": False, "keys_only": True,
252+
"reveal": False, "keys_only": False,
266253
}
267254
# ハンドラ実行中は projects/carmo に居る (CWD と PWD の両方を切り替える)
268255
assert captured["cwd"] == str(target)
@@ -273,15 +260,13 @@ def test_run_operation_list_project_chdirs_and_restores(monkeypatch, tmp_path):
273260

274261

275262
def test_run_operation_list_project_select_cancel(monkeypatch, tmp_path):
276-
"""list のプロジェクト選択を中止したら表示オプション収集にも進まない。"""
263+
"""list のプロジェクト選択を中止したら実行しない。"""
277264
from devbase.commands import env as env_mod
278265
called = []
279266
monkeypatch.setattr(env_mod, "cmd_env", lambda root, args: called.append(1) or 0)
280267
monkeypatch.setattr(menu, "select", lambda *a, **k: "project")
281268
monkeypatch.setattr(actions_env, "_select_project",
282269
lambda root: actions_env._ARG_CANCEL)
283-
monkeypatch.setattr(menu, "confirm",
284-
lambda *a, **k: pytest.fail("選択中止後に確認を求めない"))
285270
assert actions_env._run_operation(tmp_path, "list") is actions_env._ARG_CANCEL
286271
assert called == []
287272

@@ -299,20 +284,6 @@ def test_run_operation_list_scope_cancel(monkeypatch, tmp_path, scope_ret):
299284
assert called == []
300285

301286

302-
@pytest.mark.parametrize("confirm_ret", ["BACK", None])
303-
def test_run_operation_list_confirm_cancel(monkeypatch, tmp_path, confirm_ret):
304-
"""reveal の confirm で Esc は再表示 (_ARG_CANCEL)、Ctrl-C は全体中止 (None)。"""
305-
from devbase.commands import env as env_mod
306-
called = []
307-
monkeypatch.setattr(env_mod, "cmd_env", lambda root, args: called.append(1) or 0)
308-
monkeypatch.setattr(menu, "select", lambda *a, **k: "global")
309-
ret = menu.MENU_BACK if confirm_ret == "BACK" else None
310-
monkeypatch.setattr(menu, "confirm", lambda *a, **k: ret)
311-
expected = actions_env._ARG_CANCEL if confirm_ret == "BACK" else None
312-
assert actions_env._run_operation(tmp_path, "list") is expected
313-
assert called == []
314-
315-
316287
# ---------------------------------------------------------------------------
317288
# _run_operation: get / delete
318289
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)