From d8b520b78751a6c5f1aa7323df8eed41e57b8126 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sat, 30 May 2026 07:47:53 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20PLAN06-4=20=E8=A3=9C=E5=AE=8C=20+=20doc?= =?UTF-8?q?s=20+=20CHANGELOG=20+=20container=20=E9=9D=9E=E6=8E=A8=E5=A5=A8?= =?UTF-8?q?=E5=91=8A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PLAN06 で導入した project サブコマンド体系をシェル補完・ドキュメント・ CHANGELOG に反映し、container グループの非推奨を告知する。 - etc/devbase-completion.bash / etc/_devbase: - project グループ補完 (up/down/ps/login/logs/scale/build/list) を追加 - プロジェクト名補完 (_devbase_project_names: $DEVBASE_ROOT/projects/ 配下を 列挙) を project up/down/ps/logs/scale とトップレベルシノニム up/down/ps/scale に追加 - project list / top-level list の --interactive / -i 補完 - container は補完候補に残しつつ非推奨マーク (zsh description) - docs/user/cli-reference.md: - コマンド体系図・エイリアス・ショートカットを project 体系に更新 - 「project グループ」節を新設 (name 解決 / project list / --interactive / login・build が name を取らない理由 / 親シェル CWD 非汚染) - 「container グループ (非推奨)」節に置換 - docs/user/container-operations.md: - 冒頭に project 体系への移行注記、scale/logs 例を project 形へ、 project list の横断一覧 vs project ps の単体表示の役割整理を追記 - README.md / docs/user/getting-started.md: 残存する container 直接形を project へ - CHANGELOG.md: Unreleased に PLAN06 (Added: project 群 / Changed: container 非推奨) - tests/cli/test_completion.py 新規 (bash 補完を source した動作検証 8件 + zsh 静的内容/構文チェック。zsh 未インストール環境は skip) pytest: 374 passed, 1 skipped (zsh) Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 7 ++ README.md | 2 +- docs/user/cli-reference.md | 166 +++++++++++++++++++++++------- docs/user/container-operations.md | 33 +++++- docs/user/getting-started.md | 2 +- etc/_devbase | 87 +++++++++++++++- etc/devbase-completion.bash | 67 +++++++++++- tests/cli/test_completion.py | 109 ++++++++++++++++++++ 8 files changed, 423 insertions(+), 50 deletions(-) create mode 100644 tests/cli/test_completion.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e950a7..17e571e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ ## [Unreleased] ### Added +- **`devbase project` サブコマンド群を新設**しました (PLAN06)。CWD に依存せずプロジェクト名でコンテナ操作ができます。 + - `devbase project up/down/ps/logs/scale [name]` で、任意のディレクトリから `$DEVBASE_ROOT/projects/` を対象に操作できます。名前解決はラッパー (`bin/devbase`) が対象ディレクトリへ `cd` してから実行するため、シェル実装の `build` を含む全操作が名前指定で成立します(呼び出し元シェルの作業ディレクトリは変わりません)。存在しない名前はエラーになり候補が提示されます。 + - `devbase project list [--interactive|-i]` で `$DEVBASE_ROOT/projects/` 配下を `NAME` / `PLUGIN` / `STATUS` の一覧表示します。`PLUGIN` 列はシンボリックリンク先から解決するため、PLAN04 の同名衝突 suffix(例 `carmo.takemi`)が付いていても正しいプラグイン名を表示します。`--interactive` では一覧から番号で選択して起動でき、非対話環境では番号入力にフォールバックします。 + - トップレベルシノニム `devbase up/down/ps/scale [name]` / `devbase build [image]` / `devbase login [index]` / `devbase list` を整備しました(`logs` はシノニムを持たず `devbase project logs` のみ)。 + - bash / zsh のシェル補完に `project` グループとプロジェクト名補完(`$DEVBASE_ROOT/projects/` 配下を列挙)を追加しました。 + - 利用者向けドキュメント [`docs/user/cli-reference.md`](docs/user/cli-reference.md) / [`docs/user/container-operations.md`](docs/user/container-operations.md) を `project` 体系に更新しました。 - `devbase env export` / `devbase env import` で **S3 URI (`s3://bucket/key`) を入出力先として指定**できるようになりました (PLAN03-1 PR3)。 - 既定でオブジェクト単位の SSE (`aws:kms` または `AES256`) を強制し、export 時はバケット側のデフォルト暗号化も `GetBucketEncryption` で事前確認します。 - 暗号化が未設定のバケットへ export する場合は `--unsafe-allow-unencrypted-bucket` の明示が必要です (オブジェクト単位の SSE はこのフラグに関係なく常に付与されます)。 @@ -15,6 +21,7 @@ - README と環境変数ガイドからのリンクも追加しました。 ### Changed +- **`devbase container` グループを非推奨化**しました (PLAN06)。`devbase container ` は `devbase project ` のエイリアスとして当面動作しますが、実行時に非推奨警告を表示します(移行期間後のリリースで削除予定)。`[name]` 指定や `list` などの新機能は `project` 側のみで提供されます。トップレベルショートカット (`devbase up` 等) の転送先も `container` から `project` へ変更しました。 - `gs://` (GCS) スキームは **PLAN03-1 PR4 廃案** により対応しません。指定すると明示的なエラーメッセージで失敗します (旧: "未実装")。 - `lib/devbase/env/` 配下の export / import モジュールをリファクタリングしました (PLAN03-1 PR5)。公開 API (`ExportOptions`, `ImportOptions`, `export`, `import_bundle`) に互換性のない変更はありません。 - export / import で重複していた passphrase 読み取り / 既定鍵 fallback / セキュアな bytes 書き込みを `io_common.py` に集約。 diff --git a/README.md b/README.md index 9635598..fca4f1c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ devbaseは、Docker Composeを使った再現性の高い開発環境を提供 - **Pluginベースのプロジェクト管理**: 外部リポジトリからプロジェクト設定をインストール・更新 - **コンテナ化された開発環境**: Docker Composeベースで再現性の高い環境を提供 - **豊富なツールセット**: Docker CLI、AWS CLI、gcloud SDK、Terraform、Node.js、AI CLIツールがプリインストール -- **複数コンテナの並行開発**: `devbase container scale`で既存コンテナを再起動せずにスケール可能 +- **複数コンテナの並行開発**: `devbase project scale`で既存コンテナを再起動せずにスケール可能 - **データ永続化**: 名前付きボリュームでコンテナ再起動後もデータを保持 - **スナップショット管理**: `/home/ubuntu` 共通ボリュームの増分バックアップ・復元・世代管理 - **環境変数の自動収集**: `devbase env init`でAWS/Git/GCP認証情報を対話的に設定 diff --git a/docs/user/cli-reference.md b/docs/user/cli-reference.md index d099d0a..8b44b7d 100644 --- a/docs/user/cli-reference.md +++ b/docs/user/cli-reference.md @@ -11,38 +11,47 @@ graph TD A[devbase] --> B[init] A --> C[status] A --> H[shell-rc] - A --> D[container / ct] + A --> D[project] A --> E[env] A --> F[plugin / pl] A --> G[snapshot / ss] - D --> D1[up / down / login / ps / logs / scale / build] + D --> D1["up / down / login / ps / logs / scale / build [name]"] + D --> D2["list [--interactive]"] E --> E1[init / sync / list / set / get / delete / edit / project] F --> F1[list / install / uninstall / update / info / sync] F --> F2[repo add / repo remove / repo list / repo refresh] G --> G1[create / list / restore / copy / delete / rotate] ``` +> **`container` グループは非推奨になりました。** 旧 `devbase container ` は +> `devbase project ` のエイリアスとして当面動作しますが、実行時に非推奨警告を +> 表示します(移行期間後のリリースで削除予定)。新しいコマンドは `project` を使用してください。 + ### グループエイリアス 各グループには短縮形が用意されています。 -| グループ名 | エイリアス | -|-----------|-----------| -| `container` | `ct` | -| `plugin` | `pl` | -| `snapshot` | `ss` | +| グループ名 | エイリアス | 備考 | +|-----------|-----------|------| +| `plugin` | `pl` | | +| `snapshot` | `ss` | | +| `container` | `ct` | **非推奨**(`project` へ移行してください) | ### ショートカットコマンド -頻繁に使用するコンテナ操作はトップレベルから直接実行できます。これらは `container` グループに自動転送されます。 +頻繁に使用するプロジェクト操作はトップレベルから直接実行できます。これらは `project` グループに自動転送されます。 | ショートカット | 転送先 | |--------------|--------| -| `devbase up` | `devbase container up` | -| `devbase down` | `devbase container down` | -| `devbase login` | `devbase container login` | -| `devbase build` | `devbase container build` | -| `devbase ps` | `devbase container ps` | +| `devbase up [name]` | `devbase project up [name]` | +| `devbase down [name]` | `devbase project down [name]` | +| `devbase login [index]` | `devbase project login [index]` | +| `devbase build [image]` | `devbase project build [image]` | +| `devbase ps [name]` | `devbase project ps [name]` | +| `devbase scale [name] ` | `devbase project scale [name] ` | +| `devbase list` | `devbase project list` | + +> **Note:** `logs` はトップレベルシノニムを持ちません。`devbase project logs` を使用してください。 ### ユニークプレフィックスマッチング @@ -113,17 +122,43 @@ source "$(./bin/devbase shell-rc)" > **⚠ 引用符は必須**: `source $(devbase shell-rc)` のように引用符を省くと、ホームディレクトリ名に空白を含む環境(例: `/Users/foo bar/.zshrc`)で word splitting が起き `source` が失敗します。必ず `source "$(devbase shell-rc)"` の形で書いてください。 -## container (ct) グループ +## project グループ + +プロジェクト(コンテナ)のライフサイクル管理と一覧表示を行うコマンド群です。 + +### プロジェクト名指定(CWD 非依存) + +`up` / `down` / `ps` / `logs` / `scale` は省略可能な `[name]` 引数を取ります。`[name]` +を指定すると、**現在のディレクトリに依存せず** `$DEVBASE_ROOT/projects/` を対象に +操作できます。 + +```bash +# 任意のディレクトリから adminer プロジェクトを起動 +devbase project up adminer + +# 省略時は従来どおりカレントディレクトリのプロジェクトを対象にする +cd $DEVBASE_ROOT/projects/adminer && devbase project up +``` + +- `` は `$DEVBASE_ROOT/projects/` 配下のプロジェクト名(`devbase project list` で確認可能) +- 存在しない名前を指定するとエラーになり、利用可能なプロジェクト候補が表示されます +- 名前解決はラッパー (`bin/devbase`) が対象ディレクトリへ `cd` してから実行します。 + これにより `build`(シェル実装)を含む全操作が名前指定で成立します +- `devbase` は PATH 上の実行ファイルとして子プロセスで起動されるため、この `cd` が + **呼び出し元シェルの作業ディレクトリを変えることはありません** -コンテナのライフサイクル管理を行うコマンド群です。 +> **`login` / `build` は `[name]` を取りません。** これらの単一引数はそれぞれ `index` / +> `image` であり、`[name]` を許すと `project login 2` / `project build web` が誤解釈される +> ため除外しています(トップレベルシノニム `devbase build ` のみラッパーの存在性判定で +> 名前解決されます)。 -### `devbase container up` +### `devbase project up` コンテナを起動します。 ``` -devbase container up -devbase up +devbase project up [name] +devbase up [name] ``` - 起動時にスナップショットを自動作成(新世代 or 差分追加) @@ -136,23 +171,23 @@ devbase up (前回 pull 日時は `${DEVBASE_ROOT}/.cache/pulls/` の touch-file mtime で判定) - 閾値は `DEVBASE_IMAGE_MAX_AGE_DAYS` 環境変数で上書き可能(既定 7、不正値は警告して既定値) -### `devbase container down` +### `devbase project down` コンテナを停止・削除します。 ``` -devbase container down -devbase down +devbase project down [name] +devbase down [name] ``` - 停止時にスナップショットのローテーションを自動実行 -### `devbase container login` +### `devbase project login` コンテナにログインします。 ``` -devbase container login [index] +devbase project login [index] devbase login [index] ``` @@ -168,25 +203,26 @@ devbase login devbase login 2 ``` -### `devbase container ps` +### `devbase project ps` -コンテナの状態を表示します。 +対象プロジェクトのコンテナ状態を `docker compose ps` で表示します。複数プロジェクトの +横断一覧は `devbase project list` を使用してください。 ``` -devbase container ps [-a] -devbase ps [-a] +devbase project ps [name] [-a] +devbase ps [name] [-a] ``` | オプション | 説明 | |-----------|------| | `-a` | 停止中のコンテナも表示 | -### `devbase container logs` +### `devbase project logs` -コンテナのログを表示します。 +コンテナのログを表示します(トップレベルシノニムはありません)。 ``` -devbase container logs [-f] [--tail N] +devbase project logs [name] [-f] [--tail N] ``` | オプション | 説明 | @@ -196,35 +232,37 @@ devbase container logs [-f] [--tail N] ```bash # 最新50行をリアルタイムで追跡 -devbase container logs -f --tail 50 +devbase project logs -f --tail 50 ``` -### `devbase container scale` +### `devbase project scale` 既存のコンテナを再起動せずにスケールします。 ``` -devbase container scale +devbase project scale [name] +devbase scale [name] ``` | パラメータ | 必須 | 説明 | |-----------|------|------| +| `name` | いいえ | 対象プロジェクト名(省略時はカレント) | | `` | はい | コンテナ数 | ```bash # コンテナを3台に増やす -devbase container scale 3 +devbase project scale 3 -# コンテナを1台に減らす -devbase container scale 1 +# 任意のディレクトリから adminer を3台に +devbase project scale adminer 3 ``` -### `devbase container build` +### `devbase project build` コンテナイメージをビルドします。 ``` -devbase container build [image] +devbase project build [image] devbase build [image] ``` @@ -232,6 +270,58 @@ devbase build [image] |-----------|------|------| | `image` | いいえ | ビルドするイメージ名(省略時は全イメージ) | +### `devbase project list` + +`$DEVBASE_ROOT/projects/` 配下のプロジェクトを `NAME` / `PLUGIN` / `STATUS` の一覧で +表示します。 + +``` +devbase project list [--interactive|-i] +devbase list [--interactive|-i] +``` + +| オプション | 説明 | +|-----------|------| +| `--interactive` / `-i` | 一覧から番号で選択し、そのプロジェクトを `project up` で起動 | + +```bash +# 一覧表示 +devbase list + +# 一覧から選んで起動(非対話環境では番号入力にフォールバック) +devbase list -i +``` + +出力例: + +``` +NAME PLUGIN STATUS +adminer adminer running (2 containers) +carmo carmo stopped +carmo.takemi carmo-fork stopped +``` + +- `PLUGIN` 列はシンボリックリンク先から解決するため、PLAN04 の同名衝突 suffix + (例 `carmo.takemi`)が付いていても正しいプラグイン名を表示します +- `STATUS` は `running (N containers)` / `stopped` / `unknown`(docker 未起動・ + `compose.yml` 不在等で判定不能)のいずれか + +## container (ct) グループ(非推奨) + +> **非推奨:** `container` グループは `project` グループへ移行しました。`devbase container +> ` は当面 `devbase project ` のエイリアスとして動作しますが、実行時に非推奨警告を +> 表示します(移行期間後のリリースで削除予定)。`[name]` 指定や `list` などの新機能は +> `project` 側のみで提供されます。 + +```bash +# 旧(非推奨・警告が出ます) +devbase container up + +# 新(推奨) +devbase project up +devbase up +``` + ## env グループ 環境変数の管理を行うコマンド群です。詳細は [環境変数ガイド](environment-variables.md) を参照してください。 diff --git a/docs/user/container-operations.md b/docs/user/container-operations.md index 9e76fcd..d15889d 100644 --- a/docs/user/container-operations.md +++ b/docs/user/container-operations.md @@ -2,6 +2,13 @@ devbase のコンテナ管理機能について、ライフサイクル、並行開発、ボリューム構造、イメージ階層を解説します。 +> **コマンド体系について:** コンテナ操作は `devbase project ` グループ(および +> トップレベルショートカット `devbase up` 等)で行います。旧 `devbase container ` は +> 非推奨となり、`project` へのエイリアスとして警告付きで当面動作します。`project` では +> `up` / `down` / `ps` / `logs` / `scale` に `[name]` を指定することで **任意のディレクトリ +> から** 対象プロジェクトを操作できます。プロジェクト一覧は `devbase project list` を参照 +> してください。詳細は [CLI リファレンス](cli-reference.md#project-グループ) を参照。 + ## コンテナライフサイクル devbase のコンテナは以下のライフサイクルで管理されます。 @@ -75,10 +82,13 @@ CONTAINER_SCALE=2 ```bash # コンテナを3台に増やす(既存コンテナは再起動しない) -devbase container scale 3 +devbase project scale 3 # コンテナを1台に減らす -devbase container scale 1 +devbase project scale 1 + +# 任意のディレクトリから adminer を3台に +devbase project scale adminer 3 ``` ### 各コンテナへのログイン @@ -216,15 +226,28 @@ devbase ps -a ```bash # 最新のログを表示 -devbase container logs +devbase project logs # リアルタイムでログを追跡 -devbase container logs -f +devbase project logs -f # 末尾100行のみ追跡 -devbase container logs -f --tail 100 +devbase project logs -f --tail 100 +``` + +### プロジェクト一覧 + +```bash +# 全プロジェクトを NAME / PLUGIN / STATUS で一覧表示 +devbase list + +# 一覧から選択して起動(非対話環境では番号入力にフォールバック) +devbase list -i ``` +`devbase project ps` が「対象プロジェクト 1 つのコンテナ状態」を表示するのに対し、 +`devbase list` は「全プロジェクトの横断一覧」を表示します。 + ### 環境の全体像 ```bash diff --git a/docs/user/getting-started.md b/docs/user/getting-started.md index b2f5cf8..c996505 100644 --- a/docs/user/getting-started.md +++ b/docs/user/getting-started.md @@ -163,7 +163,7 @@ devbase login devbase ps # ログの確認 -devbase container logs -f +devbase project logs -f # 2番目のコンテナにログイン(並行作業) devbase login 2 diff --git a/etc/_devbase b/etc/_devbase index cb3a5cb..95e6026 100644 --- a/etc/_devbase +++ b/etc/_devbase @@ -13,6 +13,19 @@ _devbase_plugin_names() { fi } +_devbase_project_names() { + local devbase_root projects_dir + devbase_root="${DEVBASE_ROOT:-$(dirname "$(dirname "$(command -v devbase 2>/dev/null)")" 2>/dev/null)}" + projects_dir="${devbase_root}/projects" + if [[ -d "$projects_dir" ]]; then + local -a projects + # 実ディレクトリ + symlink 先がディレクトリのものを対象 + # (N: nullglob, -: symlink 追従, /: ディレクトリのみ, :t: tail)。 + projects=(${projects_dir}/*(N-/:t)) + _describe 'project' projects + fi +} + _devbase_repo_names() { local devbase_root yml devbase_root="${DEVBASE_ROOT:-$(dirname "$(dirname "$(command -v devbase 2>/dev/null)")" 2>/dev/null)}" @@ -32,15 +45,16 @@ except Exception: } _devbase() { - local -a commands container_subcommands env_subcommands + local -a commands project_subcommands container_subcommands env_subcommands local -a plugin_subcommands repo_subcommands snapshot_subcommands commands=( 'init:Initialize devbase environment' 'status:Show overall status' 'shell-rc:Print shell RC file path (for source ...)' - 'container:Manage containers' - 'ct:Manage containers (alias)' + 'project:Manage projects (CWD-independent)' + 'container:Manage containers (deprecated: use project)' + 'ct:Manage containers (deprecated alias)' 'env:Manage environment variables' 'plugin:Manage plugins' 'pl:Manage plugins (alias)' @@ -51,9 +65,22 @@ _devbase() { 'login:Login to container (shortcut)' 'build:Build container images' 'ps:Show container status (shortcut)' + 'scale:Scale containers online (shortcut)' + 'list:List projects (shortcut)' 'help:Show help' ) + project_subcommands=( + 'up:Start containers' + 'down:Stop and remove containers' + 'ps:Show container status' + 'login:Login to container' + 'logs:Show container logs' + 'scale:Scale containers online' + 'build:Build container images' + 'list:List projects (NAME / PLUGIN / STATUS)' + ) + container_subcommands=( 'up:Start containers' 'down:Stop and remove containers' @@ -107,6 +134,60 @@ _devbase() { login) _values 'index' 1 2 ;; + # トップレベルシノニム: up/down/ps/scale は [name] を取るためプロジェクト名を補完。 + up|down) + _devbase_project_names + ;; + ps) + if [[ "$words[3]" == -* || -n "$words[3]" ]]; then + _arguments '--all[Show all containers]' '-a[Show all containers]' + else + _devbase_project_names + fi + ;; + scale) + _devbase_project_names + ;; + list) + _arguments \ + '--interactive[Select a project interactively and start it]' \ + '-i[Select a project interactively and start it]' + ;; + project) + case "$words[3]" in + up|down) + _devbase_project_names + ;; + login) + _values 'index' 1 2 + ;; + scale) + _devbase_project_names + ;; + ps) + _arguments '--all[Show all containers]' '-a[Show all containers]' + _devbase_project_names + ;; + logs) + _arguments \ + '--follow[Follow log output]' \ + '-f[Follow log output]' \ + '--tail[Number of lines]:lines:' + _devbase_project_names + ;; + build) + _arguments '1:image:' + ;; + list) + _arguments \ + '--interactive[Select a project interactively and start it]' \ + '-i[Select a project interactively and start it]' + ;; + *) + _describe -t project-commands 'project command' project_subcommands + ;; + esac + ;; container|ct) case "$words[3]" in login) diff --git a/etc/devbase-completion.bash b/etc/devbase-completion.bash index fdba9df..99b898b 100644 --- a/etc/devbase-completion.bash +++ b/etc/devbase-completion.bash @@ -1,5 +1,18 @@ # bash completion for devbase +# projects/ 配下のプロジェクト名 (symlink / 実ディレクトリ) を列挙する。 +# `devbase project up ` やトップレベルシノニム `devbase up ` の +# name 補完に使う。 +_devbase_project_names() { + local devbase_root + devbase_root="${DEVBASE_ROOT:-$(dirname "$(dirname "$(command -v devbase 2>/dev/null)")" 2>/dev/null)}" + local projects_dir="${devbase_root}/projects" + if [ -d "$projects_dir" ]; then + find "$projects_dir" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) 2>/dev/null \ + | xargs -r -n1 basename 2>/dev/null + fi +} + _devbase_completions() { local cur prev words cword _init_completion 2>/dev/null || { @@ -10,7 +23,9 @@ _devbase_completions() { cword=$COMP_CWORD } - local commands="init status shell-rc container ct env plugin pl snapshot ss up down login build ps help" + local commands="init status shell-rc project container ct env plugin pl snapshot ss up down login build ps scale list help" + # project / container は同じサブコマンド群 (container は非推奨だが補完は維持)。 + local project_subcommands="up down ps login logs scale build list" local container_subcommands="up down ps login logs scale build" local env_subcommands="init sync list set get delete edit project export import" local plugin_subcommands="list install uninstall update info sync repo" @@ -26,6 +41,19 @@ _devbase_completions() { login) COMPREPLY=($(compgen -W "1 2" -- "$cur")) ;; + # トップレベルシノニム: up/down/ps/scale は [name] を取るため + # プロジェクト名を補完する (login=index / build=image は対象外)。 + up|down|ps|scale) + COMPREPLY=($(compgen -W "$(_devbase_project_names)" -- "$cur")) + ;; + list) + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "--interactive -i" -- "$cur")) + fi + ;; + project) + COMPREPLY=($(compgen -W "$project_subcommands" -- "$cur")) + ;; container|ct) COMPREPLY=($(compgen -W "$container_subcommands" -- "$cur")) ;; @@ -42,7 +70,42 @@ _devbase_completions() { ;; 3) local group="${words[1]}" - # container subcommand arguments + # project subcommand arguments (推奨グループ) + if [ "$group" = "project" ]; then + case "$prev" in + up|down) + COMPREPLY=($(compgen -W "$(_devbase_project_names)" -- "$cur")) + ;; + login) + COMPREPLY=($(compgen -W "1 2" -- "$cur")) + ;; + scale) + # `project scale N` / `project scale N` の両形。 + # name 補完を提示する (数値はユーザが直接入力)。 + COMPREPLY=($(compgen -W "$(_devbase_project_names)" -- "$cur")) + ;; + ps) + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "--all -a" -- "$cur")) + else + COMPREPLY=($(compgen -W "$(_devbase_project_names)" -- "$cur")) + fi + ;; + logs) + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "--follow -f --tail" -- "$cur")) + else + COMPREPLY=($(compgen -W "$(_devbase_project_names)" -- "$cur")) + fi + ;; + list) + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "--interactive -i" -- "$cur")) + fi + ;; + esac + fi + # container subcommand arguments (非推奨: project へ移行してください) if [ "$group" = "container" ] || [ "$group" = "ct" ]; then case "$prev" in login) diff --git a/tests/cli/test_completion.py b/tests/cli/test_completion.py new file mode 100644 index 0000000..caccd6b --- /dev/null +++ b/tests/cli/test_completion.py @@ -0,0 +1,109 @@ +"""PLAN06 Task 4: シェル補完 (bash / zsh) の回帰テスト。 + +- bash 補完を実際に source して `project` サブコマンド補完 / プロジェクト名補完 / + トップレベルシノニム補完が機能することを検証する (test_wrapper_dispatch と同方式)。 +- zsh 補完はランナー非依存にするため静的内容チェックのみ。 +""" + +from __future__ import annotations + +import os +import shutil +import subprocess +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[2] +BASH_COMPLETION = REPO_ROOT / "etc" / "devbase-completion.bash" +ZSH_COMPLETION = REPO_ROOT / "etc" / "_devbase" + + +def _bash_complete(words, cword, devbase_root): + """bash 補完を source して COMPREPLY を改行区切りで返す。""" + script = f""" +set -e +source "{BASH_COMPLETION}" +COMP_WORDS=({words}) +COMP_CWORD={cword} +_devbase_completions +printf '%s\\n' "${{COMPREPLY[@]}}" +""" + env = {**os.environ, "DEVBASE_ROOT": str(devbase_root)} + proc = subprocess.run(["bash", "-c", script], capture_output=True, text=True, env=env) + assert proc.returncode == 0, proc.stderr + return [line for line in proc.stdout.splitlines() if line] + + +@pytest.fixture +def fake_root(tmp_path): + projects = tmp_path / "projects" + projects.mkdir() + (projects / "web").mkdir() + (projects / "api").mkdir() + # symlink プロジェクト + (tmp_path / "target").mkdir() + (projects / "linked").symlink_to(tmp_path / "target") + return tmp_path + + +# --------------------------------------------------------------------------- +# bash: 構文 / 動作 +# --------------------------------------------------------------------------- + +def test_bash_completion_syntax_ok(): + proc = subprocess.run(["bash", "-n", str(BASH_COMPLETION)], + capture_output=True, text=True) + assert proc.returncode == 0, proc.stderr + + +def test_bash_project_subcommands(fake_root): + out = _bash_complete("devbase project ''", 2, fake_root) + assert set(out) >= {"up", "down", "ps", "login", "logs", "scale", "build", "list"} + + +def test_bash_project_name_completion(fake_root): + out = _bash_complete("devbase project up ''", 3, fake_root) + assert sorted(out) == ["api", "linked", "web"] + + +def test_bash_top_level_synonym_name_completion(fake_root): + """`devbase up ` がプロジェクト名を補完する。""" + out = _bash_complete("devbase up ''", 2, fake_root) + assert sorted(out) == ["api", "linked", "web"] + + +def test_bash_project_list_flags(fake_root): + out = _bash_complete("devbase project list '-'", 3, fake_root) + assert set(out) == {"--interactive", "-i"} + + +def test_bash_top_level_commands_include_project_and_list(fake_root): + out = _bash_complete("devbase ''", 1, fake_root) + assert "project" in out + assert "list" in out + # 後方互換: container も補完候補に残る + assert "container" in out + + +# --------------------------------------------------------------------------- +# 静的内容チェック (zsh は実行環境非依存にするため内容のみ確認) +# --------------------------------------------------------------------------- + +def test_zsh_completion_mentions_project_and_list(): + text = ZSH_COMPLETION.read_text() + assert "'project:Manage projects" in text + assert "_devbase_project_names" in text + assert "list:List projects" in text + + +def test_zsh_completion_marks_container_deprecated(): + text = ZSH_COMPLETION.read_text() + assert "deprecated" in text.lower() + + +@pytest.mark.skipif(shutil.which("zsh") is None, reason="zsh 未インストール") +def test_zsh_completion_syntax_ok(): + proc = subprocess.run(["zsh", "-n", str(ZSH_COMPLETION)], + capture_output=True, text=True) + assert proc.returncode == 0, proc.stderr