Skip to content

Commit 3b38330

Browse files
takemi-ohamaclaude
andauthored
release: PLAN06 devbase project サブコマンド導入 (#33)
* chore: release/PLAN06 ブランチ初期化 (Draft release PR 用) plan: issues/PLAN06_project-subcommand.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: PLAN06-1 project サブコマンド group + ハンドラ共有 + container 非推奨委譲 (#34) * chore: feature/PLAN06-project-group Draft PR 作成 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: PLAN06-1 project サブコマンド group + 共有ハンドラ + container 非推奨委譲 - cli.py: _add_project_parser を追加し up/down/ps/login/logs/scale/build に 省略可能な [name] positional を付与 - cli.py: SUBCMD_MAP / _expand_argv / _dispatch に project を追加(prefix 解決対応) - cli.py: ショートカット (up 等) は非推奨の container ではなく共有 cmd_project へ委譲 - container.py: cmd_container 本体を _dispatch_lifecycle に抽出し cmd_project (推奨) / cmd_container (非推奨 warning + 委譲) で共有 - name は up/scale の project_name へ畳み込み(ディレクトリ解決 / cd は PR2 で実装) - tests/cli/test_project_dispatch.py: parser / scale positional 非曖昧性 / prefix 解決 / dispatch ルーティング / 非推奨 warning を検証 runtime 挙動は従来と同等(リネーム + 委譲のみ、cd なし)。wrapper (bin/devbase) の project ルーティング + name 解決は PR2 (Task 2) に分離。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): project login/build の引数曖昧さ解消 + name 未対応の明示警告 PR #34 round1 レビュー対応 (codex / gemini)。 - project login / build から `[name]` positional を削除し、単一 positional を index / image として扱う (旧 container login <index> / build <image> と一致)。 `project login 2` が name='2' で index=1 にログインしてしまう曖昧さ、 `project build web` が name='web', image=None で image 指定が無視される問題を解消。 - 共有ディスパッチャ _dispatch_lifecycle で、PR1 未対応の name 指定時に 「カレントディレクトリの compose に作用する」旨を warning 出力。 name を黙って無視して意図しない compose に作用するのを防ぐ (up/scale は既に name を受け取る経路のため対象外)。 - name 解決自体は設計どおり PR2 スコープ (docs/plans/PLAN06_project_name.md)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): PLAN06 round2 レビュー対応 (wrapper project 到達不能 + up/scale 警告ほか) - bin/devbase: resolve_command 候補と dispatch case に `project` を追加。 これまで `devbase project ...` が `*)` 節で unknown command になっていた問題を解消。 - _dispatch_lifecycle: name 指定時の未実装警告を up/scale でも出すよう変更。 PR1 では全サブコマンドが CWD の compose に作用し name によるディレクトリ解決を しないため、up/scale だけ無警告だと「指定 project に作用した」と誤解を与えるため。 - SHORTCUTS: 死んだ group 要素 (`('container', sub)`) を除去し subcommand 文字列に簡略化。 - _add_project_parser: login/build が name を取らない設計意図と PR2 での --name 方針を docstring に明記。 - tests: wrapper の project ルーティング (test_wrapper_dispatch.py) と up/scale name 警告のテストを追加。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(cli): _dispatch_lifecycle の up/scale name 警告テストを追加 round2 のレビュー対応 (container.py:100) で up/scale も name 指定時に未実装 警告を出すようにしたため、その回帰防止テストを追加する。前 commit の編集が 既存テストの記法不一致で取り込めていなかったため本 commit で補完する。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): top-level build ショートカットを wrapper の実経路 (shell) に揃える bin/devbase は top-level `build` を shell の cmd_build (devbase-base 依存検出 + 2 段ビルド + --no-cache 対応) に委譲しているが、Python 側は `build` を SHORTCUTS / _add_shortcuts / help epilog で `project build` ショートカットとして広告していた。 Python の project build は単純な compose build であり、wrapper 経由の `devbase build` は実際には shell の cmd_build を通る (共有ハンドラ経路にならない) ため、広告と実経路が 乖離していた (codex major)。 Python の project build へ委譲する案 (codex 案1) は shell の base-image ビルド オーケストレーションを失う回帰になるため採らず、codex 案2 を採用: Python 側の SHORTCUTS / _add_shortcuts / help epilog / _expand_argv から top-level `build` を 除外し、実経路 (shell cmd_build) と広告を一致させた。project build / container build サブコマンド自体は維持。整合性回帰テスト 5 件を追加。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): PLAN06 deferred 3 件対応 (parser 共通化 / shortcut name 受理 / 登録順序整理) ユーザ指示により cross-review で deferred としていた 3 件を本 PR で対応する。 1. [minor] _add_container_parser / _add_project_parser の重複 project / container で完全一致する login / build サブコマンド定義を _add_login_subparser / _add_build_subparser に括り出し共有化。分岐する up/down/ps/logs/scale の [name] 構成は呼び出し側に残し可読性を維持。 2. [minor] トップレベルショートタットの [name] 受理と伝播 up/down/ps/scale を project サブコマンドと同様に省略可能な [name] positional を受理するよう _add_shortcuts を更新。ショートカット経由でも name が _dispatch → cmd_project → _dispatch_lifecycle まで伝播する経路を通した (name の実解決は PR2、PR1 では未対応 warning を出す)。 3. [nit] commands リストへの project/container 登録順序 _expand_argv の commands リストの並び (_create_parser 登録順と一致、 group 直後に alias を隣接配置、project=推奨 を container=非推奨 より前) と prefix 解決が順序非依存である旨をコメントで明示。 回帰テスト: shortcut の [name] 受理・伝播、login/build の project/container 一致を test_project_dispatch.py に追加。pytest 全 suite 348 passed。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: PLAN06-2 wrapper cd によるプロジェクト名解決 + トップレベルシノニム (#35) * feat: PLAN06-2 wrapper cd によるプロジェクト名解決 + トップレベルシノニム 任意の CWD から `devbase project up <name>` / `devbase up <name>` でプロジェクトを 指定操作できるよう、プロジェクト名解決を実装する (PLAN06 Task 2 / 方針 A: wrapper cd)。 bin/devbase (核心): - maybe_cd_project を追加。project/container <sub> <name> 及びトップレベルシノニム <sub> <name> の <name> が $DEVBASE_ROOT/projects/<name> に実在する場合のみ cd し、 COMPOSE_PROJECT_NAME / ./env を cd 後に再設定、argv から name を strip して下流へ。 - 実在性ベースの判定により login <index> / build <image> / scale <N> の既存 positional と曖昧にならない (実在プロジェクト名のときだけ name 扱い)。 - build は shell 実装 (cmd_build) のため、この wrapper cd だけが build の name 解決手段になる (方針 A の核心)。dispatch を name strip 後の _DEVBASE_ARGS 経由に変更。 lib/devbase/commands/container.py (Python 防御フォールバック): - PR1 の「name 解決は未実装」warning を撤去し、_resolve_project_name で projects/<name> 解決 → os.chdir (wrapper が cd 済みなら冪等 no-op) + COMPOSE_PROJECT_NAME 上書き。chdir は _dispatch_lifecycle で一括実施 (down/login/logs は project_name 引数を持たないため per-handler では救えない)。 - 存在しない name はエラー + 候補一覧を提示。 tests: - 新規 test_project_name_resolution.py: wrapper cd / argv strip / 曖昧性回避 / Python 解決 / 候補提示を検証。 - PR1 の未実装 warning 系テストを新挙動 (解決 → handler / 解決失敗 → abort) へ更新。 - build dispatch の文字列アサートを _DEVBASE_ARGS 形式へ追従。 全 359 テスト pass。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(wrapper): project build/login の name 解決を抑止し positional 衝突を回避 `project`/`container` グループの wrapper name 解決を parser が name positional を 持つサブコマンド (up/down/ps/logs/scale) に限定する。従来は全サブコマンドで第3 引数を無条件に project 名解決していたため、`project build web` の image=web や `project login web` の index が実在プロジェクト名と一致した瞬間に strip されて 別操作へ化ける衝突があった (codex round1 major 指摘)。 トップレベルシノニム (build/login 含む) は Python parser を経ず shell cmd_build / wrapper cd だけが name 指定手段であるため、従来どおり「実在 project なら cd」の 存在性ベース判定を維持する。 project build/login グループの衝突回避を固定する回帰テスト 2 件を追加。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(pr35): deferred minor 2件 — env source 方針の注記 + 候補一覧 truncate - bin/devbase: maybe_cd_project の source ./env に設計判断コメントを追記。 set -a の export を後続へ引き継ぐため subshell 化はできず、env は信頼境界内 かつ既に L24 でも source されるため新規リスク増は無いことを明記。 - container.py: _report_unknown_project の候補一覧を先頭 20 件 + 「... 他 M 件」 に truncate (多数プロジェクト環境での 1 行肥大を回避)。 - tests: truncate / 上限内非省略 の回帰テストを追加。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(pr35): gemini round2 minor 2件 — Python フォールバックの env source 整合 + list 同期注記 - container.py: _resolve_project_name に _load_project_env を追加し、wrapper の `source ./env` と同等に project env を os.environ へ反映。wrapper を経ない 直接起動 (`python -m devbase.cli project up <name>`) でも CONTAINER_SCALE 等の project 固有変数が欠落しないようにする。name 指定は env 由来 COMPOSE_PROJECT_NAME より優先 (env 反映後に上書き)。単純な KEY=VALUE のみ解釈し変数展開/コマンド置換は 非サポート (安全側)。 - bin/devbase / cli.py: _PROJECT_NAME_SUBCOMMANDS / _NAME_RESOLVABLE_SHORTCUTS と cli.py の parser 定義 (_add_project_parser / SHORTCUTS) の同期が必要な旨を両側に注記。 - tests: env 反映 / name 優先 / env 不在時の堅牢性 の回帰テスト 3件追加。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: PLAN06-3 project list 一覧表示 + --interactive 選択起動 (#36) * feat: PLAN06-3 project list 一覧表示 + --interactive 選択起動 `devbase project list` / トップレベルシノニム `devbase list` を新設し、 $DEVBASE_ROOT/projects/ 配下を NAME / PLUGIN / STATUS で一覧表示する。 - lib/devbase/commands/project.py (新規): - _resolve_plugin_name: symlink 先から plugin 名を解決。PLAN04 の同名衝突 suffix (carmo.takemi) はリンク名のみに付きリンク先 dir は素の <proj> の ままなので、リンク先を辿ることで suffix 有無に関わらず正しく解決する。 - list_projects: projects/ 配下 (symlink/実dir/broken symlink) を列挙。 status は status._container_status_for を共有 (取得不能は unknown)。 - cmd_project_list: 整列テーブル表示 / --interactive で番号入力選択 → project up 起動 (新規依存を足さず stdlib input、非TTY は EOFError graceful)。 - lib/devbase/commands/status.py: - per-entry の _container_status_for を抽出し project list と共有 (cmd_status の挙動は不変)。 - lib/devbase/cli.py: - project list サブコマンド (--interactive/-i) + トップレベル list シノニム - SUBCMD_MAP / _expand_argv / dispatch に list を同期 (DEVBASE_ROOT 必須) - bin/devbase: - resolve_command 候補 + dispatch case に list を追加 (name 解決対象外) - tests: test_project_list.py 新規 (25件) + wrapper dispatch に list 経路 4件 pytest: 395 passed (baseline 366 + 29) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(project): PR#36 レビュー対応 — l→login 互換 / status 並列化 / 堅牢性 / 再入力 - [major/互換性] `list` 追加で ambiguous になった `devbase l` を `login` に維持 (bin/devbase resolve_command + cli.py TOP_PREFIX_PREFERENCES)。`li` は list のまま。 - [major/性能] list_projects の `docker compose ps` を ThreadPoolExecutor で並列化 (cwd= で完結し global chdir せずスレッド安全)。 - [minor/堅牢性] _resolve_plugin_name が `/projects/proj` 等で `/`・`..` を plugin 名に返さず None に。 - [minor/UX] _interactive_select_and_up を誤入力 (数値以外/範囲外) で再入力ループに。 - 回帰テスト 6 件追加。pytest 402 passed。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(project): PR#36 round2 — KeyboardInterrupt 中止 / prefix preference 同期テスト - _interactive_select_and_up: Ctrl+C (KeyboardInterrupt) を捕捉し traceback を出さず中止 (rc=0) として扱う (minor / 堅牢性) - TOP_PREFIX_PREFERENCES の bin/devbase と cli.py の乖離防止のため、 両者の preference 対応表が一致することを検証する同期テストを追加 (major / 正確性) - 回帰テスト 2 件追加 (keyboard_interrupt_aborts / synced_with_cli) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(project): PR#36 deferred nit — _load_project_env と shell source の仕様乖離を明文化 gemini round2 review-body の将来課題推奨 (Python パーサと shell source の env 解釈が乖離し得る) に対し、仕様統一はリスクが大きいため制約のドキュメント化で 対応する。 - _load_project_env docstring に shell ``source`` との具体的な乖離ケース (変数展開 / コマンド置換 / 行中クォート / インラインコメント) を note 追記。 いずれも wrapper を経ない直接起動のフォールバック時のみ影響する旨を明記。 - 乖離挙動を pin する回帰テスト test_load_project_env_diverges_from_shell_source を追加。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: PLAN06-4 補完 + docs + CHANGELOG + container 非推奨告知 (#37) * feat: PLAN06-4 補完 + docs + CHANGELOG + container 非推奨告知 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) <noreply@anthropic.com> * fix(completion): bash top-level ps の -a フラグ補完を project ps と対称化 軽量レビュー nit 対応。bash 補完の top-level シノニム `devbase ps` は プロジェクト名のみ補完し -a/--all フラグを補完していなかった (zsh / project ps は分岐済み)。$cur が `-*` のときフラグ、それ以外は name を補完するよう project ps (cword 3) と同じ挙動に揃え、回帰テスト2件を追加した。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(docs,completion): PR#37 レビュー指摘3件対応 - README CLI体系セクションを project 主グループ + container 非推奨注記へ更新 (cli-reference.md と整合) - bash 補完に cword>=4 分岐を追加し project ps/logs の name 後フラグ補完に対応 - _devbase_project_names を find -L ... -type d に変更し dir/dir-symlink のみ列挙 (壊れた/ファイル symlink を除外、zsh と整合) - 回帰テスト3件追加 (壊れた symlink 除外 / project ps/logs フラグ補完) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(completion,docs): PR#37 Round3 指摘2件対応 - completion: _devbase_project_names の `xargs -r`(GNU 拡張) を排除し、 find -L ... -type d の各行を POSIX パラメータ展開 ${p##*/} で basename 化。 BSD/macOS でも候補が空にならず移植性を確保 (symlink dir 限定は維持)。 - docs: cli-reference.md のトップレベルシノニム名前解決の記述を正確化。 bin/devbase の _NAME_RESOLVABLE_SHORTCUTS に login が含まれ、login <name> も build <name> 同様に存在性判定で名前解決されるため両方を明記。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs,completion: PR#37 Round4 minor 指摘4件対応 (最終スイープ) - etc/devbase-completion.bash: `list` / `project list` の `-*` ガードを外し 位置引数を取らないサブコマンドで常に --interactive 候補を出す (zsh と整合) - docs/user/cli-reference.md: Mermaid D1 ノードから login [index] / build [image] を分離し [name] を取らないことを明示 - etc/_devbase: `project ps` / `project logs` の name 補完を _arguments の positional (1:name:) に統合し、名前入力済みでも候補が出る問題を解消 - docs/user/cli-reference.md / README.md: `devbase build` が project グループ ではなく bin/devbase の shell 実装 cmd_build へ直接委譲される旨を明記 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(env): プロジェクト切替時に呼び出し元固有 env キーをクリア (codex指摘) 別プロジェクト内から `devbase project up other` 等を実行した際、対象 env を 上書き source するだけだと呼び出し元プロジェクトにしか無い env キー (例: DEV_SERVICE_NAME) が残留し、対象プロジェクトへ誤って引き継がれていた。 - bin/devbase: 起動時に呼び出し元 (初期 CWD) の env キーを env_var_keys() で記録し、 maybe_cd_project の対象 env source 前に unset。共通キーは対象 env が再設定するため 対象側の値が勝つ。 - lib/devbase/commands/container.py: Python フォールバック (_resolve_project_name / _load_project_env) でも同様に、chdir 前の呼び出し元 env キーのうち対象 env に 無いものを unset。 - tests: wrapper / Python 両経路の回帰テストを追加。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(wrapper): ct alias を name 解決経路に統合 + 衝突注意を強化 PR #33 cross-review round2 codex 指摘対応。 - ct alias 修正 (minor, bin/devbase:319): name 解決 case の `project|container)` を `project|container|ct)` に拡張。 `ct up <name>` が container と同じ strip/chdir 経路を通るようにし、 従来 `unrecognized arguments: <name>` になっていた回帰を解消。 alias `ct` 自体は cli.py 側で container へ解決されるため Python へは `ct` のまま渡す。回帰テスト test_wrapper_ct_up_name_cds_and_strips を追加。 - 衝突注意の強化 (major=却下に伴うドキュメント補強): login/build/scale の存在性ベース name 解決は意図的設計のため挙動は維持。 positional 引数 (index/image/service) が実在プロジェクト名と衝突した際に project 解決が優先され引数解釈が変わる footgun を bin/devbase コメントと docs/user/cli-reference.md に明記。回避策 (対象プロジェクト内で実行 / 明示 切り替え) も併記。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0344610 commit 3b38330

18 files changed

Lines changed: 2862 additions & 161 deletions

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
## [Unreleased]
66

77
### Added
8+
- **`devbase project` サブコマンド群を新設**しました (PLAN06)。CWD に依存せずプロジェクト名でコンテナ操作ができます。
9+
- `devbase project up/down/ps/logs/scale [name]` で、任意のディレクトリから `$DEVBASE_ROOT/projects/<name>` を対象に操作できます。名前解決はラッパー (`bin/devbase`) が対象ディレクトリへ `cd` してから実行するため、シェル実装の `build` を含む全操作が名前指定で成立します(呼び出し元シェルの作業ディレクトリは変わりません)。存在しない名前はエラーになり候補が提示されます。
10+
- `devbase project list [--interactive|-i]``$DEVBASE_ROOT/projects/` 配下を `NAME` / `PLUGIN` / `STATUS` の一覧表示します。`PLUGIN` 列はシンボリックリンク先から解決するため、PLAN04 の同名衝突 suffix(例 `carmo.takemi`)が付いていても正しいプラグイン名を表示します。`--interactive` では一覧から番号で選択して起動でき、非対話環境では番号入力にフォールバックします。
11+
- トップレベルシノニム `devbase up/down/ps/scale [name]` / `devbase build [image]` / `devbase login [index]` / `devbase list` を整備しました(`logs` はシノニムを持たず `devbase project logs` のみ)。
12+
- bash / zsh のシェル補完に `project` グループとプロジェクト名補完(`$DEVBASE_ROOT/projects/` 配下を列挙)を追加しました。
13+
- 利用者向けドキュメント [`docs/user/cli-reference.md`](docs/user/cli-reference.md) / [`docs/user/container-operations.md`](docs/user/container-operations.md)`project` 体系に更新しました。
814
- `devbase env export` / `devbase env import`**S3 URI (`s3://bucket/key`) を入出力先として指定**できるようになりました (PLAN03-1 PR3)。
915
- 既定でオブジェクト単位の SSE (`aws:kms` または `AES256`) を強制し、export 時はバケット側のデフォルト暗号化も `GetBucketEncryption` で事前確認します。
1016
- 暗号化が未設定のバケットへ export する場合は `--unsafe-allow-unencrypted-bucket` の明示が必要です (オブジェクト単位の SSE はこのフラグに関係なく常に付与されます)。
@@ -15,6 +21,7 @@
1521
- README と環境変数ガイドからのリンクも追加しました。
1622

1723
### Changed
24+
- **`devbase container` グループを非推奨化**しました (PLAN06)。`devbase container <sub>``devbase project <sub>` のエイリアスとして当面動作しますが、実行時に非推奨警告を表示します(移行期間後のリリースで削除予定)。`[name]` 指定や `list` などの新機能は `project` 側のみで提供されます。トップレベルショートカット (`devbase up` 等) の転送先も `container` から `project` へ変更しました。
1825
- `gs://` (GCS) スキームは **PLAN03-1 PR4 廃案** により対応しません。指定すると明示的なエラーメッセージで失敗します (旧: "未実装")。
1926
- `lib/devbase/env/` 配下の export / import モジュールをリファクタリングしました (PLAN03-1 PR5)。公開 API (`ExportOptions`, `ImportOptions`, `export`, `import_bundle`) に互換性のない変更はありません。
2027
- export / import で重複していた passphrase 読み取り / 既定鍵 fallback / セキュアな bytes 書き込みを `io_common.py` に集約。

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ devbaseは、Docker Composeを使った再現性の高い開発環境を提供
1111
- **Pluginベースのプロジェクト管理**: 外部リポジトリからプロジェクト設定をインストール・更新
1212
- **コンテナ化された開発環境**: Docker Composeベースで再現性の高い環境を提供
1313
- **豊富なツールセット**: Docker CLI、AWS CLI、gcloud SDK、Terraform、Node.js、AI CLIツールがプリインストール
14-
- **複数コンテナの並行開発**: `devbase container scale`で既存コンテナを再起動せずにスケール可能
14+
- **複数コンテナの並行開発**: `devbase project scale`で既存コンテナを再起動せずにスケール可能
1515
- **データ永続化**: 名前付きボリュームでコンテナ再起動後もデータを保持
1616
- **スナップショット管理**: `/home/ubuntu` 共通ボリュームの増分バックアップ・復元・世代管理
1717
- **環境変数の自動収集**: `devbase env init`でAWS/Git/GCP認証情報を対話的に設定
@@ -75,12 +75,14 @@ devbaseのコマンドは4つのグループにまとめられています。
7575

7676
| グループ | 略記 | 説明 |
7777
|---------|------|------|
78-
| `container` | `ct` | コンテナ管理(up / down / login / ps / logs / scale / build) |
78+
| `project` | | プロジェクト管理(up / down / login / ps / logs / scale / build / list|
7979
| `env` || 環境変数管理(init / sync / list / set / get / delete / edit / project / export / import) |
8080
| `plugin` | `pl` | プラグイン管理(list / install / uninstall / update / info / sync / repo) |
8181
| `snapshot` | `ss` | スナップショット管理(create / list / restore / copy / delete / rotate) |
8282

83-
- **ショートカット**: `up`, `down`, `login`, `build`, `ps` はトップレベルから直接使用可能
83+
> **`container`(略記 `ct`)グループは非推奨です。** `devbase project <sub>` のエイリアスとして当面動作しますが、非推奨警告を表示します。新しいコマンドは `project` を使用してください。
84+
85+
- **ショートカット**: `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#ショートカットコマンド)
8486
- **プレフィックス略記**: `devbase p l``devbase plugin list`
8587
- **トップレベルコマンド**: `init`, `status`, `shell-rc`
8688

bin/devbase

Lines changed: 170 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,27 @@ done
1212
SCRIPT_DIR="$(cd -P "$(dirname "$SCRIPT_PATH")" && pwd)"
1313
DEVBASE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
1414

15+
# env ファイルから「定義されている変数キー名」だけを抽出する (値は読まない)。
16+
# project 切替時に呼び出し元プロジェクト固有の env キーを unset するために使う。
17+
# Python フォールバック側 (_load_project_env) の KEY=VALUE パーサと同じ前提で
18+
# 解釈する: 行頭の空白除去 → 先頭 `#` はコメント → 任意の `export ` 接頭辞除去 →
19+
# `=` の左辺をキーとして採用。シェル展開やコマンド置換は解釈しない (値は無視)。
20+
env_var_keys() {
21+
local file="$1"
22+
[ -f "$file" ] || return 0
23+
local raw line key
24+
while IFS= read -r raw || [ -n "$raw" ]; do
25+
line="${raw#"${raw%%[![:space:]]*}"}" # 行頭空白除去
26+
case "$line" in ''|'#'*) continue ;; esac
27+
line="${line#export }"
28+
line="${line#"${line%%[![:space:]]*}"}" # export 後の空白除去
29+
case "$line" in *=*) ;; *) continue ;; esac
30+
key="${line%%=*}"
31+
key="${key%"${key##*[![:space:]]}"}" # キー末尾空白除去
32+
[ -n "$key" ] && printf '%s\n' "$key"
33+
done < "$file"
34+
}
35+
1536
# Environment setup
1637
export DOCKER_GID=$( [ "$(uname)" = "Darwin" ] && echo "0" || grep docker /etc/group | cut -d: -f3 )
1738
export COMPOSE_PROJECT_NAME=$(basename "$PWD")
@@ -21,6 +42,16 @@ export COMPOSE_PROJECT_NAME=$(basename "$PWD")
2142
# なる。compose は同階層の .env を自動で読むため wrapper 側で project .env を
2243
# source する必要は無い。
2344
[ -f "${DEVBASE_ROOT}/.env" ] && set -a && source "${DEVBASE_ROOT}/.env" && set +a
45+
46+
# 呼び出し元 (初期 CWD) の env で定義された変数キーを記録しておく。
47+
# project 切替 (maybe_cd_project) 時に「呼び出し元プロジェクトにしか無い変数」を
48+
# unset してから対象 env を source するために使う。これをしないと、対象 env に
49+
# 同名キーが無い場合に呼び出し元プロジェクト固有の値 (例: DEV_SERVICE_NAME) が
50+
# 残留し、`devbase project up other` を別プロジェクト内から実行した際に誤って
51+
# 引き継がれてしまう (codex 指摘 / PR#33 bin/devbase:235)。
52+
# env パース仕様は env_var_keys() のコメント参照。
53+
_CALLER_ENV_KEYS=""
54+
[ -f "env" ] && _CALLER_ENV_KEYS="$(env_var_keys ./env)"
2455
[ -f "env" ] && set -a && source ./env && set +a
2556

2657
# Export for Python modules
@@ -168,16 +199,82 @@ run_python() {
168199
# Resolve abbreviated command to full command name via unique prefix matching
169200
resolve_command() {
170201
local input="$1"
171-
local commands="init status shell-rc container ct env plugin pl snapshot ss up down login build ps scale help"
202+
local commands="init status shell-rc project container ct env plugin pl snapshot ss up down login build ps scale list help"
172203
local matches=()
173204
for cmd in $commands; do
174205
[[ "$cmd" == "$input"* ]] && matches+=("$cmd")
175206
done
176207
if [ ${#matches[@]} -eq 1 ]; then
177208
echo "${matches[0]}"
178-
else
179-
echo "$input" # no match or ambiguous -> return as-is
209+
return
180210
fi
211+
# ambiguous の場合の後方互換 preference。`list` 追加で `l` が login/list の
212+
# 両方にマッチするようになったため、既存の `devbase l` → `login` を維持する。
213+
# cli.py の TOP_PREFIX_PREFERENCES と同期させること。
214+
if [ ${#matches[@]} -gt 1 ]; then
215+
local preferred=""
216+
case "$input" in
217+
l) preferred="login" ;;
218+
esac
219+
if [ -n "$preferred" ]; then
220+
for m in "${matches[@]}"; do
221+
[ "$m" = "$preferred" ] && { echo "$preferred"; return; }
222+
done
223+
fi
224+
fi
225+
echo "$input" # no match or ambiguous -> return as-is
226+
}
227+
228+
# ===================================================================
229+
# Project name resolution (PLAN06 Task 2)
230+
# ===================================================================
231+
# `devbase project <sub> <name>` および同義のトップレベルシノニム
232+
# `devbase <sub> <name>` の <name> が $DEVBASE_ROOT/projects/<name> に実在する
233+
# 場合、そのディレクトリへ cd し COMPOSE_PROJECT_NAME / env を再設定する。
234+
# これにより任意の CWD からプロジェクトを指定してコンテナ操作できる。
235+
#
236+
# 重要: `build` は shell 実装 (cmd_build) が CWD で動くため、この wrapper cd
237+
# だけが build の name 解決手段になる (PLAN06 方針 A の核心)。Python 側 chdir
238+
# フォールバックでは build を救えない。
239+
#
240+
# <name> 判定は projects/ 配下の実在性で行う。これにより `login <index>` /
241+
# `build <image>` / `scale <N>` の既存 positional と曖昧にならない: 実在する
242+
# プロジェクト名のときだけ name として解釈し cd + strip する。実在しなければ
243+
# 引数はそのまま下流 (Python パーサ) へ渡し、Python 側で index/image/scale
244+
# あるいは「存在しない name」エラーとして扱わせる。
245+
246+
# name 候補を受け取り projects/ 配下に実在すれば cd + env 再設定して 0 を返す。
247+
maybe_cd_project() {
248+
local name="${1:-}"
249+
case "$name" in -*|"") return 1 ;; esac # フラグ・空は name ではない
250+
local target="${DEVBASE_ROOT}/projects/${name}"
251+
[ -d "$target" ] || return 1
252+
cd "$target" || return 1
253+
export COMPOSE_PROJECT_NAME="$name"
254+
# cd 後にプロジェクトの env を再 source (初期 CWD で読んだ値を上書き)。
255+
# project の .env (dotfile) は CRLF / 特殊文字対策で意図的に source しない
256+
# 方針を踏襲する (冒頭コメント参照)。
257+
#
258+
# 注意: env は環境変数定義のみを想定したファイルであり subshell ではなく
259+
# 現プロセスで source する。これは wrapper 冒頭 L23-24 の env 読み込みと同一
260+
# 意図 — set -a で export した変数を後続の run_python / cmd_build へ引き継ぐ
261+
# ため、変数が親プロセスに残らない subshell 化はできない。代償として env 内に
262+
# `exit` 等があると wrapper ごと終了するが、env は (a) プロジェクト所有者が
263+
# 管理する信頼境界内のファイルで (b) 元々 L24 で初期 CWD でも source される
264+
# ため、ここで新たなリスクが増えるわけではない。万一 exit を含む env を読んで
265+
# も「該当プロジェクトの操作が中断する」だけで他プロジェクトへ波及しない。
266+
#
267+
# 重要 (codex 指摘 / PR#33 bin/devbase:235): 単に対象 env を source するだけ
268+
# では、呼び出し元プロジェクトの env にしか無いキー (例: DEV_SERVICE_NAME) が
269+
# 残留し、対象プロジェクトへ誤って引き継がれる。対象 env を読む前に、起動時に
270+
# 記録した呼び出し元 env 固有キーを unset してクリーンな状態を作る。
271+
# (対象 env が同名キーを定義していれば直後の source で再設定される。)
272+
local _k
273+
for _k in $_CALLER_ENV_KEYS; do
274+
unset "$_k"
275+
done
276+
[ -f "env" ] && set -a && source ./env && set +a
277+
return 0
181278
}
182279

183280
# Resolve the command (skip flags like --version, -V, -h, --help)
@@ -187,14 +284,81 @@ case "$_resolved_cmd" in
187284
*) _resolved_cmd="$(resolve_command "$_resolved_cmd")" ;;
188285
esac
189286

287+
# name 解決: 実在するプロジェクト名を検出したら cd し、その token を argv から
288+
# 取り除いた配列 _DEVBASE_ARGS を組み立てる。検出しなければ素通し。
289+
# name 候補の位置:
290+
# project|container <sub> <name> -> $3 (サブコマンドは保持)
291+
# トップレベルシノニム <sub> <name> -> $2
292+
#
293+
# 重要 (PLAN06 codex 指摘対応): `project`/`container` グループでは parser が
294+
# `name` positional を持つサブコマンド (`up`/`down`/`ps`/`logs`/`scale`) に限定
295+
# して $3 を name 解決する。`project login` / `project build` は単一 positional が
296+
# index / image (旧 container 互換) であり parser が name を受け付けない
297+
# (cli.py の _add_login_subparser / _add_build_subparser 参照)。これらで $3 を
298+
# name strip すると、`project build web` の image=web や `project login web` の
299+
# index 引数が実在プロジェクト名と一致した瞬間に消えて別操作へ化けるため除外する。
300+
#
301+
# トップレベルシノニム (`build`/`login` を含む) は従来どおり「実在 project なら
302+
# cd」方針を維持する: トップレベル `build`/`login` は Python parser を経由せず
303+
# shell cmd_build / wrapper cd だけが name 指定の手段であり、`build carmo` /
304+
# `login carmo` を「そのプロジェクトを操作」と解釈する設計 (存在性ベース判定)。
305+
_DEVBASE_ARGS=("${@:2}")
306+
# 同期注意 (メンテナンス性): 下記 2 リストは cli.py の parser 定義に対応する。
307+
# _PROJECT_NAME_SUBCOMMANDS = `project`/`container` で `name` positional を
308+
# 受け付けるサブコマンド集合。cli.py の _add_project_parser で
309+
# `add_argument('name', ...)` を持つもの (up/down/ps/logs/scale) と一致させる。
310+
# login/build は index/image 互換のため意図的に除外 (上のコメント参照)。
311+
# _NAME_RESOLVABLE_SHORTCUTS = トップレベルシノニムのうち「実在 project なら cd」
312+
# を許すもの。cli.py の SHORTCUTS 経由で project サブコマンドへ写像される
313+
# 集合 + shell 実装の build を含む。
314+
# cli.py 側でサブコマンドを追加/削除した際は両リストの更新漏れに注意すること
315+
# (cli.py の _add_project_parser / SHORTCUTS にも対の注記あり)。
316+
#
317+
# ⚠ 衝突注意 (footgun): トップレベルシノニムの name 解決は「存在性ベース」で
318+
# 行うため、本来 positional 引数として渡したい値が実在プロジェクト名
319+
# ($DEVBASE_ROOT/projects/<name>) と一致した場合、その引数が name と解釈され
320+
# project 解決 (cd) が優先されて引数の意味が変わる。具体的には:
321+
# - `devbase login <index>` の index が実在プロジェクト名と一致
322+
# (例: projects/2 が存在する状態で `devbase login 2`) → index=2 ではなく
323+
# project `2` への cd になり、login の対象が変わる。
324+
# - `devbase build <image>` の image が実在プロジェクト名と一致
325+
# (例: projects/web が存在する状態で `devbase build web`) → image=web では
326+
# なく project `web` への cd になり、ビルド対象が変わる。
327+
# - `devbase scale <service>` の service 引数も同様に化けうる。
328+
# これはトップレベル build/login/scale を「そのプロジェクトを操作」と解釈する
329+
# 意図的設計 (存在性ベース判定) のトレードオフであり、挙動としては仕様である。
330+
# 回避策: 衝突時は対象プロジェクトのディレクトリ内で実行するか、明示的に
331+
# そのプロジェクトへ切り替えてから (cd 済みの状態で) コマンドを実行すること。
332+
# こうすれば name 解決トークンを与える必要がなくなり、index/image/service を
333+
# 意図どおり渡せる。
334+
_PROJECT_NAME_SUBCOMMANDS=" up down ps logs scale "
335+
_NAME_RESOLVABLE_SHORTCUTS=" up down ps scale login build "
336+
case "$_resolved_cmd" in
337+
project|container|ct)
338+
# `ct` は container の alias (cli.py: add_parser('container', aliases=['ct']))。
339+
# name 解決経路でも container と同じ strip/chdir を通すため分岐に含める。
340+
# _resolved_cmd は `ct` のまま python に渡してよい (cli.py 側で alias 解決済み)。
341+
if [[ "$_PROJECT_NAME_SUBCOMMANDS" == *" ${2:-} "* ]] \
342+
&& maybe_cd_project "${3:-}"; then
343+
_DEVBASE_ARGS=("${2:-}" "${@:4}")
344+
fi
345+
;;
346+
*)
347+
if [[ "$_NAME_RESOLVABLE_SHORTCUTS" == *" $_resolved_cmd "* ]] \
348+
&& maybe_cd_project "${2:-}"; then
349+
_DEVBASE_ARGS=("${@:3}")
350+
fi
351+
;;
352+
esac
353+
190354
case "$_resolved_cmd" in
191355
# Python-implemented commands
192356
--version|-V)
193357
run_python "$@" ;;
194-
init|status|shell-rc|container|ct|env|plugin|pl|snapshot|ss|up|down|login|ps|scale)
195-
run_python "${_resolved_cmd}" "${@:2}" ;;
358+
init|status|shell-rc|project|container|ct|env|plugin|pl|snapshot|ss|up|down|login|ps|scale|list)
359+
run_python "${_resolved_cmd}" "${_DEVBASE_ARGS[@]}" ;;
196360
# Shell-implemented commands
197-
build) shift; cmd_build "$@" ;;
361+
build) cmd_build "${_DEVBASE_ARGS[@]}" ;;
198362
# Help and unknown
199363
-h|--help|help|"") run_python "--help" ;;
200364
*) echo "Error: unknown command '$1'" >&2; exit 1 ;;

0 commit comments

Comments
 (0)