Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 19 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,24 @@ devbaseは、Docker Composeを使った再現性の高い開発環境を提供
- **データ永続化**: 名前付きボリュームでコンテナ再起動後もデータを保持
- **スナップショット管理**: `/home/ubuntu` 共通ボリュームの増分バックアップ・復元・世代管理
- **環境変数の自動収集**: `devbase env init`でAWS/Git/GCP認証情報を対話的に設定
- **対話的なプロジェクト選択**: `devbase list` で矢印キー移動・名前での絞り込みに対応した TUI メニューから起動対象を選べます。起動中のプロジェクトは「再起動 (up) / 再ビルド (rebuild) / 停止 (down)」を選択できます
- **対話的なプロジェクト選択**: `devbase list` TUI メニュー(矢印キー移動・名前絞り込み対応)から起動対象を選択。起動中なら再起動 (up) / 再ビルド (rebuild) / 停止 (down) も選べます
- **キャッシュ無効リビルド**: `devbase rebuild [name]` で `docker compose build --no-cache` 相当のイメージ再ビルドができます

## クイックスタート

### ワンライナーインストール(推奨)
devbase 本体を**インストール**し、続けて**プラグインを導入してプロジェクトを起動**します。インストールはワンライナー(推奨)か手動のどちらかを選んでください。

### 1. インストール

#### ワンライナー(推奨)

```bash
curl -fsSL https://dl.basex.jp/i | bash && . ~/devbase/bin/rc
```

`~/devbase` に clone(既存なら更新) `devbase init` まで自動実行(uv の自動導入・PATH/補完の登録・`plugins.yml` 生成を含む)したうえで、末尾の `&& . ~/devbase/bin/rc` で**いま開いている端末**にも devbase を通します(`&&` 以降はパイプのサブシェルではなく呼び出し元シェルで実行されるため、その場で `devbase` が使えます)。新しく開くターミナルは init の rc 追記により自動で有効です
`~/devbase` に clone(既存なら更新)して `devbase init` まで自動実行します(uv の自動導入・PATH/補完の登録・`plugins.yml` 生成を含む)末尾の `. ~/devbase/bin/rc` で**いま開いている端末**でも `devbase` が即使えます(新しく開くターミナルは自動で有効)

> 配置先を `DEVBASE_INSTALL_DIR` で変えた場合は後半の `~/devbase/bin/rc` も同じパスに合わせてください。`. ~/devbase/bin/rc` を省いた `... | bash` だけでも導入は完了します(その場合は完了メッセージの案内に従ってください)
> 配置先を `DEVBASE_INSTALL_DIR` で変えた場合は `~/devbase/bin/rc` も同じパスに合わせてください。`. ~/devbase/bin/rc` を省いた `... | bash` だけでも導入は完了します。

環境変数で挙動を上書きできます。

Expand All @@ -44,29 +48,26 @@ DEVBASE_INSTALL_DIR=~/work/devbase DEVBASE_INSTALL_REF=v1.2.3 \
bash -c "$(curl -fsSL https://dl.basex.jp/i)"
```

> **⚠ `curl | bash` を実行する前に**: 中身を確認したい場合は、いったん保存してから実行してください。
>
> ```bash
> curl -fsSL https://dl.basex.jp/i -o install.sh
> less install.sh # 内容を確認
> bash install.sh
> ```

### 手動セットアップ
#### 手動

```bash
# 1. クローンと初期化
git clone https://github.com/devbasex/devbase.git
cd devbase
./bin/devbase init
. ./bin/rc # いまのシェルで devbase を有効化(PATH / 補完)
```

# 2. Pluginのインストール
### 2. プラグインの導入とプロジェクト起動

インストール方法を問わず、以降の手順は共通です。

```bash
# プラグインのインストール
devbase plugin repo add user/repo # リポジトリ登録(init でサンプルレジストリ devbasex/devbase-samples は自動登録済み)
devbase plugin install <name> # Plugin名でインストール

# 3. プロジェクトの起動
cd projects/your-project
# プロジェクトの起動
cd ~/devbase/projects/your-project # 手動セットアップでは clone 先 (例: ./projects/your-project)
devbase env init # 環境変数の設定(初回のみ)
devbase up # コンテナを起動(初回はイメージビルドを含むため時間がかかります)
devbase login # コンテナにログイン
Expand Down Expand Up @@ -118,7 +119,7 @@ devbaseのコマンドは4つのグループにまとめられています。

> **`container`(略記 `ct`)グループは非推奨です。** `devbase project <sub>` のエイリアスとして当面動作しますが、非推奨警告を表示します。新しいコマンドは `project` を使用してください。

- **ショートカット**: `up [name]`, `down [name]`, `login [index]`, `build [image]`, `ps [name]`, `scale [name] <num>`, `list` はトップレベルから直接使用可能(`project` グループへ自動転送。`logs` はシノニムを持ちません)。ただし `build` のみ例外で、`project` グループ(Python 実装)ではなく `bin/devbase` のシェル実装 `cmd_build` へ直接委譲されます(詳細は [CLI リファレンス](docs/user/cli-reference.md#ショートカットコマンド))
- **ショートカット**: `up [name]`, `down [name]`, `login [index]`, `build [image]`, `ps [name]`, `scale [name] <num>`, `list` はトップレベルから直接使用可能(`project` グループへ自動転送。`logs` はシノニムを持ちません)。なお `build` のみ挙動が一部異なります(詳細は [CLI リファレンス](docs/user/cli-reference.md#ショートカットコマンド))
- **プレフィックス略記**: `devbase p l` → `devbase plugin list`
- **トップレベルコマンド**: `init`, `status`

Expand Down
4 changes: 2 additions & 2 deletions docs/user/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ curl -fsSL https://dl.basex.jp/i | bash && . ~/devbase/bin/rc

1. `~/devbase` に devbase を clone します(既に devbase が clone 済みなら `git pull --ff-only` で更新)。
2. clone 先で `devbase init` を 1 回実行します(uv の自動導入・PATH/補完の登録・`plugins.yml` 生成を含む)。
3. 末尾の `&& . ~/devbase/bin/rc` を**いま開いている端末**で実行し(`&&` 以降はパイプのサブシェルではなく呼び出し元シェルで動くため)、その端末で即座に `devbase`(PATH / 補完)が使える状態にします。新しく開くターミナルは init の rc 追記により自動で有効です。
3. 末尾の `&& . ~/devbase/bin/rc` を**いま開いている端末**で実行し、その端末で即座に `devbase`(PATH / 補完)が使える状態にします。新しく開くターミナルは init の rc 追記により自動で有効です。

`env init`(手順 7)は対話が必要なため、ワンライナーでは**実行せず案内のみ**です。完了後に手動で実行してください。配置先を `DEVBASE_INSTALL_DIR` で変えた場合は、`~/devbase/...` を同じパスに合わせてください。

Expand Down Expand Up @@ -82,7 +82,7 @@ cd devbase
. ./bin/rc
```

`bin/rc` を source すると、いま開いているシェルに devbase の PATH と補完がその場で適用されます(`init` が rc ファイルに追記する内容と同じ有効化を、現在のシェルへ即時反映します)。`devbase` 前提シェルである bash / zsh のどちらでも同じく `.`(= `source`)で読み込めます。
`bin/rc` を source すると、`init` が rc ファイルに追記するのと同じ有効化(devbase の PATH と補完)が現在のシェルへ即時反映されます。bash / zsh のどちらでも `.`(= `source`)で読み込めます。

> **Note:** 新しいターミナルを開いた場合は `init` が rc に追記したブロックで自動的に有効化されるため、この手順は不要です。

Expand Down
196 changes: 196 additions & 0 deletions issues/PLAN31_2_list-tui-unified.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# PLAN31_2: `devbase list` TUI の統合UI化

> 元 issue: `issues/i31.md` 第2項
> ステータス: 着手可(設計確定 2026-06-09・既存コード精読済み)
> 関連: PLAN31_1 (init は installer に吸収)、PLAN06 (`project` 群)
> 関連 skill: `/ndf:issue-plan-strategy`, `/ndf:implementation-plan`

## 1. 背景と目的

現状の `devbase list` TUI(`lib/devbase/commands/project.py`)は
**プロジェクト選択 → up/rebuild/down サブメニュー**まで(i29〜i32 の到達点)。
ゴールは **`devbase` のほぼ全操作を TUI から実行可能**にすること。`init` は
PLAN31_1 に吸収のため対象外。範囲は **全コマンド群を階層メニューでフル統合**
(ユーザー確認済み 2026-06-09)。

## 2. 既存コード調査結果(実装の核心)

### 2.1 全ハンドラは `args.subcommand` + `getattr(args, …)` 駆動

CLI の実体は argparse だが、各グループハンドラは Namespace を
`subcommand` で分岐し属性を `getattr` で読むだけ。**TUI は `types.SimpleNamespace`
を組んで既存ハンドラを直接呼べる**(ロジック二重実装不要)。実証パターンが既にある:

```python
# project.py:174-185 _start_project_action
from devbase.commands.container import cmd_project
return cmd_project(types.SimpleNamespace(subcommand=action, name=name, scale=None))
```

### 2.2 ハンドラのシグネチャと委譲経路

| カテゴリ | エントリ | シグネチャ | 出典 |
|---|---|---|---|
| project ライフサイクル | `cmd_project(args)` → `_dispatch_lifecycle` | `args`(Namespace) | `container.py:309-311`, `265-306` |
| env | `cmd_env(devbase_root, args)` | `(Path, Namespace)` | `env.py:18-` |
| plugin | `cmd_plugin(devbase_root, args)` | `(Path, Namespace)` | `plugin.py:23-` |
| snapshot | `cmd_snapshot(devbase_root, args)` | `(Path, Namespace)` | `snapshot.py:22-` |
| status | `cmd_status(devbase_root)` | `(Path,)` 引数なし | `status.py:184` |

> 重要な発見: `_dispatch_lifecycle` は `getattr(args,'name',None)` が真なら
> **subcommand を問わず** `_resolve_project_name` で対象 `projects/<name>` へ
> `chdir` する(`container.py:278-284`, `205-262`)。つまり TUI は project 系で
> **一律 `name=<選択プロジェクト>` を渡せば chdir が効く**。`login`/`build` は
> parser に `name` が無い(`cli.py:89-110`)が、Namespace に `name` を載せれば
> chdir は発動するので問題なし。

### 2.3 各サブコマンドが読む属性(cli.py parser 定義より逆算)

TUI が組む `SimpleNamespace` に必要な属性。CLI 実行と差異を出さないための契約表。

| サブコマンド | 必要属性(既定) | 出典 (cli.py) | scope |
|---|---|---|---|
| project up | `name`, `scale`(None) | `_dispatch_lifecycle:288` | CWD(name で chdir) |
| project down | `name` | `:290` | 〃 / 破壊的 |
| project login | `name`, `index`("1") | `:291`, `99` | 〃 |
| project ps | `name`, `all`(False) | `:292`, `167-169` | 〃 |
| project logs | `name`, `follow`(False), `tail`(None) | `:293`, `171-174` | 〃 |
| project scale | `name`, `new_scale`(int) | `:295`, `178-180` | 〃 |
| project build | `name`, `image`(None) | `:297`, `110` | 〃 |
| project rebuild | `name` | `:298`, `188-190` | 〃 |
| env init | `reset`(False) | `env.py:23`, `cli:219-220` | global .env / 対話 |
| env list | `global_only`,`project_only`,`reveal`,`keys_only` | `env.py:25-29`, `cli:224-231` | global+project |
| env set | `assignment`("K=V"), `project`(False) | `env.py:30-31`, `cli:233-235` | `--project` 時 CWD |
| env get/delete | `key` | `env.py:32-33`, `cli:237-241` | global |
| env edit/sync/project | (なし) | `cli:222,243-244` | edit/project は CWD |
| env export/import | `dest`/`source` 他多数 | `env.py:392-430`, `cli:246-328` | global+project |
| plugin list | `available`(False) | `plugin.py:29`, `cli:337-338` | global |
| plugin install | `source`, `link`, `install_all` | `plugin.py:31-33`, `cli:341-345` | global |
| plugin uninstall/info | `name` | `plugin.py:34,36`, `cli:348-355` | global / 破壊的(uninstall) |
| plugin update | `name`(None) | `plugin.py:35`, `cli:351-352` | global |
| plugin sync/migrate | (なし) | `cli:357-360` | global |
| plugin repo add | `repo_command='add'`, `url`, `name`(None) | `plugin.py:176,185`, `cli:366-368` | global |
| plugin repo remove | `repo_command='remove'`, `name`, `force` | `cli:370-373` | global / 破壊的 |
| plugin repo list/refresh | `repo_command`, (`name` for refresh) | `cli:375-378` | global |
| snapshot create | `name`(None), `full`(False) | `snapshot.py:29-30`, `cli:387-389` | global |
| snapshot restore | `name`, `point`(None) | `snapshot.py:33-34`, `cli:393-395` | global / 破壊的 |
| snapshot copy | `name`, `new_name` | `snapshot.py:36-37`, `cli:398-400` | global |
| snapshot delete | `name` | `snapshot.py:38`, `cli:402-403` | global / 破壊的 |
| snapshot rotate | `keep`(3) | `snapshot.py:39`, `cli:405-406` | global |

### 2.4 既存 TUI 資産(PR1 で再利用・移送する)

- questionary は任意依存。未導入時はフォールバック (`project.py:26-31`, `319-331`)。
- メニュー部品: `_show_menu`/`_show_action_menu`/`_with_escape_cancel`/`_with_escape_back`/
`_add_escape_binding`/`_MENU_BACK` 番兵(`project.py:195-289`)。
- ナビ規約: **Esc=トップ復帰**(`_with_escape_cancel`), **←=1階層戻る**(`_with_escape_back`,
Left+Esc)(`project.py:214-245`)。Ctrl-C=中止(rc=0)。
- 非 TTY 判定 → 一覧表示フォールバック(`project.py:391-394`)。
- プロジェクト一覧と状態: `list_projects`/`_print_table`(`project.py:75-130`)、
状態は `docker ps` 1 回集計 `status._running_counts_by_project`(`project.py:105`)。

## 3. 設計方針

### 3.1 アーキテクチャ(モジュール切り出し)

`project.py` 肥大化を避け `lib/devbase/tui/` パッケージへ分離:

```text
lib/devbase/tui/
menu.py # questionary ラッパ・_MENU_BACK・escape binding・引数収集ヘルパ
# (text/select/confirm/int/path + 入力検証ループ)
app.py # トップ階層メニュー & カテゴリ routing。devbase_root を保持
dispatch.py # SimpleNamespace 生成 + 既存ハンドラ呼び出しの薄い共通関数
actions_project.py / actions_env.py / actions_plugin.py
actions_snapshot.py / actions_status.py
```

- `cmd_project_list`(`project.py:378`)は **新 `tui.app.run(devbase_root, args)` への入口**に置換。
- `dispatch.py` は `_start_project_action`(`project.py:174-185`)を一般化:
`dispatch_lifecycle(subcommand, name, **attrs)` / `dispatch_group(handler, devbase_root, subcommand, **attrs)`。
- questionary 不在 / 非 TTY 時は現状フォールバック(project 一覧の番号入力)を維持し、
全 TUI を要求された場合は「CLI を使ってください」と縮退案内。

### 3.2 `devbase list` の入口挙動(後方互換に配慮)

`devbase list` は新トップメニューを開く。互換性のため **「プロジェクト操作」を
先頭・既定ハイライト**にし、Enter 連打で従来の project 選択フローに到達できるようにする
(PR1 のレビュー観点に「既存挙動の非回帰」を明記)。`--no-interactive`/`--plain` は
従来どおり一覧テーブルのみ。

### 3.3 project スコープ依存の扱い

`env set --project` / `env project` / `env edit` は CWD(プロジェクトディレクトリ)で
動く。これらを TUI から実行する場合は **先にプロジェクトを選ばせて chdir**(2.2 の
name→chdir 機構、または `os.chdir`)してからハンドラを呼ぶ。env/plugin/snapshot の
global 操作は DEVBASE_ROOT 上で動くため chdir 不要。

### 3.4 破壊的操作の確認

`down`/`env delete`/`plugin uninstall`/`plugin repo remove`/`snapshot delete`/
`snapshot restore` は実行前に `menu.confirm()` で確認(2.3 の「破壊的」印)。

### 3.5 状態遷移

```mermaid
stateDiagram-v2
[*] --> Top
Top --> Project: プロジェクト操作(既定)
Top --> Env: 環境変数
Top --> Plugin: プラグイン
Top --> Snapshot: スナップショット
Top --> Status: ステータス
Project --> Arg: 引数/確認が必要
Env --> Arg
Plugin --> Arg
Snapshot --> Arg
Arg --> Exec: 確定
Arg --> Project: ← キャンセル
Exec --> Top: 完了後 復帰
Project --> Top: Esc
Env --> Top: Esc
Plugin --> Top: Esc
Snapshot --> Top: Esc
Status --> Top: Esc
Top --> [*]: Esc / Ctrl-C
```

## 4. PR 分割計画

PR1 で土台(`tui/` + メニューエンジン + 既存挙動の移送)。PR2〜5 は release 上で
並行(各カテゴリは別 `actions_*.py` で衝突しにくい)。

| PR # | branch 名 | 概要 | 主な変更 | 依存 | 並行 |
|---|---|---|---|---|---|
| 1 | `feature/PLAN31_2-tui-framework` | `tui/` 新設・`menu`/`dispatch`/`app`・トップ階層メニュー・Esc/←/Ctrl-C 規約移送・引数収集ヘルパ・**既存 project up/down/rebuild を非回帰移送**・`cmd_project_list` を入口に置換 | `tui/*`, `project.py` | なし | ○ |
| 2 | `feature/PLAN31_2-project-ops` | project に login/ps/logs/scale/build 追加(index/all/follow+tail/new_scale(int 検証)/image(`containers/` から選択)) | `tui/actions_project.py` | PR1 | × |
| 3 | `feature/PLAN31_2-env-ops` | env: init/list/set/get/delete/edit/sync/project/export/import。project スコープ系は事前 chdir | `tui/actions_env.py` | PR1 | ○ |
| 4 | `feature/PLAN31_2-plugin-ops` | plugin: list/install/uninstall/update/info/sync/migrate + repo add/remove/list/refresh | `tui/actions_plugin.py` | PR1 | ○ |
| 5 | `feature/PLAN31_2-snapshot-status` | snapshot: create/list/restore/copy/delete/rotate + status 閲覧 | `tui/actions_snapshot.py`, `actions_status.py` | PR1 | ○ |

```text
release branch: release/PLAN31_2
base branch: main
```

## 5. テスト計画

- **単体(monkeypatch)**: 既存テストが `_show_menu`/`_show_action_menu` を
monkeypatch する手法(`project.py` docstring 参照)を踏襲。`menu.*` を差し替えて
選択値を注入し、各 action が **2.3 の契約どおりの `SimpleNamespace`** を組んで
ハンドラを呼ぶことを検証(ハンドラは mock)。状態遷移(Esc 復帰 / ← 戻り /
Ctrl-C 中止 / 完了後トップ復帰)も検証。
- **非 TTY / questionary 不在**: 一覧フォールバック・縮退案内に落ちること。
- **破壊的操作**: confirm を介すこと(拒否で実行されない)。
- **結合(release)**: list→project up、env set→get、plugin list、snapshot create→list
を実コンテナ / dummy plugin で通し確認。既存 pytest(400+ passed)非回帰維持。

## 6. 留意点 / リスク

- **引数契約の同期**: 2.3 の表は cli.py parser と一対一。CLI 側で引数が変わると TUI が
壊れるため、PR ごとに該当 parser を再確認(PLAN06 で `login=index`/`build=image` に
整理済みの曖昧さに注意)。可能なら契約を 1 箇所に定数化し parser との同期テストを足す。
- **入口挙動変更**: `devbase list` がトップメニュー化する点は破壊的変更なので、PR1 で
「プロジェクト操作」既定ハイライトと `--plain` 維持により muscle-memory を保全。
- **切り出しの競合**: `project.py` は i29〜i32 で頻繁に更新。PR1 は現行ロジックを保全
移送し、差分レビューしやすい単位を維持する。
Loading
Loading