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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

## [Unreleased]

### Added
- **`devbase up` 後に dev コンテナへ接続した VS Code を自動オープン**できるように
しました (PLAN31_3)。`DEVBASE_OPEN_EDITOR=1`(既定 OFF)で有効化、`devbase up
--open` / `--no-open` で都度上書き。`/work/$GIT_REPO` をワークスペースとして開きます。
ローカル / WSL(Windows 側)/ VS Code Remote-SSH 統合ターミナル(手元クライアント側)
を自動判別し、素の SSH では手元で実行するコマンドを提示します。エディタは
`DEVBASE_EDITOR`(既定 `code`)で変更可能。詳細: `docs/user/environment-variables.md`。

### Changed
- **シェル有効化を `bin/rc` の source に統一**しました (PLAN31_1)。`devbase init` 後に
いま開いているシェルへ devbase(PATH / 補完)を即時適用するには
Expand Down
26 changes: 26 additions & 0 deletions docs/user/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,32 @@ devbase はホストマシンの認証情報を自動収集し、コンテナ内

ユーザー名のみで秘密情報ではありません。SSH 鍵やリモートログインの有効化はホスト側でユーザーが別途設定する前提です。`devbase env sync` 実行時には、未設定のキーのみ既定値で補完されます(既存値は上書きしません)。

## `devbase up` 後のエディタ自動オープン

`devbase up` 完了後、dev コンテナへ接続した VS Code を自動で開けます(VS Code の「Attach to Running Container」を CLI から起動)。`/work/$GIT_REPO` をワークスペースとして開きます。

これらは `devbase env init` の収集対象外で、プロジェクトの `env` か `$DEVBASE_ROOT/.env` に手書きする devbase 動作設定です。

| キー | 説明 |
|------|------|
| `DEVBASE_OPEN_EDITOR` | 真(`1`/`true`/`yes`/`on`)で `up` 後にエディタを開く(既定: OFF) |
| `DEVBASE_EDITOR` | 起動コマンド(既定: `code`)。`cursor` / `code-insiders` 等も可 |
| `DEVBASE_OPEN_INDEX` | scale 時に開く dev インスタンス番号(既定: `1`) |

都度の上書きは CLI フラグで行います: `devbase up --open` / `devbase up --no-open` / `devbase up --open-index N`(env より優先)。

### 実行コンテキスト別の挙動

| コンテキスト | 挙動 |
|------|------|
| ローカル端末(Mac/Linux) | ローカル VS Code が開く |
| WSL 端末 | Windows 側 VS Code が開く(`code` ラッパ経由) |
| VS Code の Remote-SSH 統合ターミナル | **クライアント側(手元)の VS Code** が開く(`code` シムが委譲) |
| 手元から素の SSH(VS Code 外)で接続中 | クライアントへ自動で開く公式手段が無いため、手元で実行する `code --folder-uri ...` コマンドを提示 |
| CI / 非対話(非 TTY) / `code` 不在 | 理由を表示してスキップ(`up` 自体は成功) |

> SSH 越しに「手元の VS Code」を自動で開きたい場合は、手元の VS Code から **Remote-SSH で接続した統合ターミナル内**で `devbase up` を実行してください。そのターミナルの `code` はクライアント側 VS Code に委譲するため、リモートホスト上のコンテナへ接続した窓が手元に開きます。

## ソースファイル変更検出

devbase はソースファイル(`~/.aws/config` 等)のハッシュを `.env.sources.yml` で管理しています。
Expand Down
171 changes: 171 additions & 0 deletions issues/PLAN31_3_up-open-editor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# PLAN31_3: `devbase up` 後に dev コンテナへ接続した VS Code を自動で開く

> 元 issue: `issues/i31.md` 第3項
> ステータス: 計画(2026-06-13 作成 / 未着手)
> 関連: PLAN31_1 (installer)、PLAN31_2 (list TUI 統合)、PLAN06 (`project` 群)
> 関連 skill: `/ndf:issue-plan-strategy`, `/ndf:implementation-plan`, `/ndf:investigation-rules`

## 1. 背景と目的

`devbase up` でコンテナ起動後、ユーザは別途 VS Code を開いて手動で
「Attach to Running Container」する必要がある。これを **`up` 完了時に自動で
dev コンテナへ接続した VS Code を開く**ことで起動〜開発開始の導線を短縮する。

ゴール(issue 文言):

- コンテナ起動後、devcontainer 機能で dev コンテナに接続した VS Code を開く
- `/work/{repository name}`(= `/work/$GIT_REPO`)をワークスペースとして開く
- **WSL 環境では Windows 側の VS Code を開く**
- **(ユーザ追加要件 2026-06-13)SSH 接続時は SSH クライアント側の VS Code を開く。**
例: Windows→WSL→SSH で Mac に接続し Mac 側で `devbase` を実行 → Windows の
VS Code が dev コンテナに繋がって開く(可能な範囲で)。

## 2. 実現可否調査結果(エビデンス)

> `/ndf:investigation-rules`: 「できる/できない」の結論には一次情報の裏取りを必須とする。

### 2.1 一貫機構 — `code` CLI への委譲

VS Code は **統合ターミナル内で `VSCODE_IPC_HOOK_CLI`(unix socket)を自動設定**し、
リモート/ローカルの `code` コマンドはこの socket 経由で**「このフォルダを開け」を
クライアント側 VS Code に IPC で委譲**する。WSL では `code` ラッパが `code.exe`
(Windows 側)を起動する。したがって **`code --folder-uri <uri>` を PATH 上の
`code` で叩く**だけで、実行コンテキストに応じて正しいクライアントへ窓が開く。

### 2.2 コンテナ attach URI

```
vscode-remote://attached-container+<hex>/work/$GIT_REPO
```

`<hex>` は **`{"containerName":"/<実コンテナ名>"}` を UTF-8 hex 化**した文字列。
(単純な名前の hex ではない点に注意。Docker 内部のコンテナ名は先頭 `/` 付き。)

### 2.3 ネスト authority は公式未サポート

`ssh-remote+<host>` と `attached-container+...` を 1 本の URI に合成する記法は
**存在しない**(microsoft/vscode#242489 は *Closed as not planned*)。
→ 「リモートホスト上のコンテナ」を単発 `code` で直接指定する手段は無い。

### 2.4 結論(実行コンテキスト別マトリクス)

| コンテキスト | 自動オープン | 機構 / 根拠 |
|---|---|---|
| Mac/Linux ローカル端末 | ✓ | ローカル `code` が attach URI を解決 |
| WSL 端末 | ✓ (Windows VS Code) | `code` ラッパ→`code.exe`、Docker Desktop のコンテナへ attach |
| VS Code **Remote-SSH 統合端末**(リモート=Mac) | ✓ (クライアント側) | `code` シム + `VSCODE_IPC_HOOK_CLI`。シムは既にクライアントへ接続済みなのでネスト URI 不要で attached-container を解決し、**クライアント(Windows)に窓が開く** |
| plain SSH(WSL→ssh→Mac 等、VS Code 外) | ✗ → コマンド表示 | IPC hook 無し。公式にクライアントへ push 不可(§2.3)。手元で叩く `code` コマンドを提示するのが上限 |
| CI / 非TTY / `code` 不在 | ✗ → info スキップ | エディタ起動の前提を満たさない |

→ ユーザ理想チェーンは **「手元 VS Code で Remote-SSH→Mac に入った統合ターミナルで
`devbase up`」の場合に自動成立**。plain ssh の場合は正直にコマンド提示で degrade する。

## 3. 既存コード調査結果

| 項目 | 事実 | 出典 |
|---|---|---|
| `up` の最終工程 | `[5/6]` 後に deploy script→「Deploy completed」で終了。**`[6/6]` を追加**して開く | `commands/container.py:407-424` |
| 実コンテナ名 | scale 生成 compose が `container_name = ${COMPOSE_PROJECT_NAME}-{dev}-{index}` を全インスタンスに設定(決定的) | `volume/compose.py:149` |
| ワークスペース | `WORK_DIR=/work/$GIT_REPO`(例 `/work/adminer`) | project `env`, `docs/plugin-dev/quickstart.md:103` |
| dev service 名 | `DEV_SERVICE_NAME` or `dev` | `volume/compose.py:16-18` |
| エディタ既定 | `env edit` は `$EDITOR`(既定 `vi`) を使用 | `commands/env.py:333` |
| TUI 委譲 | ハンドラは `SimpleNamespace` 駆動で TUI から直呼び可能 | PLAN31_2 §2.1 |

実コンテナ名は決定的だが、compose バージョン差異への保険として
**`docker compose ps --format json` で instance 1 の `Name` を取得**し、失敗時は
`{COMPOSE_PROJECT_NAME}-{dev}-1` へフォールバックする。scale>1 では既定で
**instance 1** を開く(`--open-index N` で上書き可)。

## 4. 設計

### 4.1 新規モジュール `lib/devbase/editor/opener.py`

責務を純粋関数に分離してテスト可能にする:

- `detect_context() -> EditorContext` — env から判定:
`is_tty`(stdout.isatty), `in_vscode`(`VSCODE_IPC_HOOK_CLI`),
`is_wsl`(`WSL_DISTRO_NAME` or `/proc/version` に `microsoft`),
`is_ssh`(`SSH_CONNECTION`/`SSH_CLIENT`/`SSH_TTY`), `is_darwin`(`uname`)
- `resolve_editor_cmd() -> list[str] | None` — 既定 `code`。`DEVBASE_EDITOR`
優先、なければ `code`→(無ければ `$EDITOR`)。`shutil.which` で実在確認
- `build_attach_uri(container_name, workdir) -> str` —
`{"containerName":"/<name>"}` を hex 化し attach URI を組む
- `resolve_container_name(...) -> str` — `docker compose ps` 優先+決定的フォールバック
- `decide_action(ctx, editor) -> OpenPlan` — マトリクス(§2.4)を 1 関数に集約。
返り値は `launch`(直接起動) / `print_command`(コマンド提示) / `skip`(理由付き)
- `open_editor(...)` — `decide_action` に従い `subprocess.Popen`(非ブロッキング) /
メッセージ出力。例外は warning に握り潰し `up` 本体を絶対に失敗させない

```mermaid
flowchart TD
A[up 完了 / open 要求] --> B{code/editor 実在?}
B -- no --> S1[skip: 導入を案内]
B -- yes --> C{非TTY/CI?}
C -- yes --> S2[skip: info]
C -- no --> D{in_vscode?<br/>VSCODE_IPC_HOOK_CLI}
D -- yes --> L[launch: code --folder-uri<br/>ローカル/WSL/Remote-SSHシムが委譲]
D -- no --> E{is_ssh?}
E -- no --> L
E -- yes --> P[print_command:<br/>手元VS Codeで実行するattach URLを提示<br/>+Remote-SSH端末からの実行を案内]
```

### 4.2 `cmd_up` への統合

`[5/6]` 後、deploy script 実行後に `[6/6] Opening editor...` を追加。
`open_editor(project_name, scale, index=open_index, mode=open_mode)` を呼ぶ。
**戻り値で `up` の rc を変えない**(エディタ起動失敗はデプロイ成功を覆さない)。

### 4.3 設定・CLI・TUI

| 層 | 追加 | 既定 |
|---|---|---|
| env/config | `DEVBASE_OPEN_EDITOR`(真偽), `DEVBASE_EDITOR`(コマンド), `DEVBASE_OPEN_INDEX` | §6 の決定に従う |
| CLI flag | `up` / `project up` に `--open` / `--no-open`, `--open-index N` | env を上書き |
| TUI | project up アクションに「起動後エディタを開く」を反映(PLAN31_2 経路) | env 既定踏襲 |

env 解釈は既存 `_parse_env_assignment`(`container.py:121`)に合わせる。
新キーは `env/keys.py` と `docs/user/environment-variables.md` に追記。

## 5. 決定事項(2026-06-13 ユーザ確認済み)

| # | 論点 | 決定 | 備考 |
|---|---|---|---|
| D1 | 自動オープンの既定 | ✅ **env `DEVBASE_OPEN_EDITOR` で制御(未設定時 OFF)+ `--open`/`--no-open` で都度上書き** | 暴発回避を最優先。プロジェクト env に 1 行書けば常時 ON にできる |
| D2 | 接続方式 | ✅ **Attach to Running Container(§2.2 URI)** | devbase project は devcontainer.json 非依存。現構成にそのまま乗る |
| D3 | エディタ | ✅ 既定 `code`、`DEVBASE_EDITOR` で上書き | `cursor` 等も同 URI スキームで動作 |
| D4 | scale>1 の対象 | ✅ instance 1(`--open-index` で変更) | |
| D5 | PR 構成 | ✅ **単一 PR(案A)** | §6 参照 |

## 6. PR 構成(単一 PR)

差分は cmd_up 統合+新規 editor モジュール+CLI/env+docs+テストで中規模
(~400 行目安・結合度高)。Step 2 の単一 PR 条件に合致するため release 運用は取らない。

| branch | 概要 |
|---|---|
| `feature/PLAN31_3-up-open-editor` | editor モジュール+cmd_up `[6/6]`+CLI/env+docs+テスト一括 → main へ |

## 7. テスト計画

- 単体(実 docker/VS Code 不要・env monkeypatch):
`detect_context`(WSL/SSH/VSCODE/Darwin の各組合せ)、
`build_attach_uri`(hex が `{"containerName":"/name"}` と一致)、
`resolve_editor_cmd`(`DEVBASE_EDITOR`/`code`/不在)、
`decide_action`(§2.4 マトリクス全分岐=launch/print/skip)
- `cmd_up` 統合: `open_editor` を mock し **rc が常に 0 のまま**であること、
`--no-open`/`DEVBASE_OPEN_EDITOR=0` で呼ばれないこと
- 既存 706 passed を維持

## 8. リスク・未確定

- **plain SSH では自動オープン不可**(§2.3 公式未サポート)。コマンド提示で degrade。
この制約は README に明記する
- VS Code Remote-SSH 統合端末でのクライアント側 attach は実機検証が必要
(`/ndf:investigation-rules`: 実機未検証の挙動は「推定」と明示)
- `code` ラッパの非ブロッキング起動が `up` プロセス終了をブロックしないこと確認

## 9. 参考(一次情報)

- microsoft/vscode#242489(ネスト authority not planned)
- attach URI / hex payload: cspotcode.com "Attach VSCode to container from CLI"
- `VSCODE_IPC_HOOK_CLI` の委譲挙動: VS Code remote troubleshooting docs
26 changes: 23 additions & 3 deletions lib/devbase/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ def _add_name_arg(parser):
`project <sub> [name]` とトップレベルショートカットで同一定義を共有する。
"""
parser.add_argument('name', nargs='?', default=None, help='Project name')
return parser


def _add_open_args(parser):
"""`up` に エディタ自動オープン関連フラグを登録する (PLAN31_3)。

三状態フラグ: ``--open`` (True) / ``--no-open`` (False) / 未指定 (None →
env ``DEVBASE_OPEN_EDITOR`` に委ねる)。``project up`` / ``container up`` /
トップレベル ``up`` で共有する。
"""
parser.add_argument('--open', dest='open_editor', action='store_true',
default=None,
help='Open editor attached to the dev container after start '
'(overrides DEVBASE_OPEN_EDITOR)')
parser.add_argument('--no-open', dest='open_editor', action='store_false',
help='Do not open editor (overrides DEVBASE_OPEN_EDITOR)')
parser.add_argument('--open-index', dest='open_index', type=int, default=None,
metavar='N',
help='Container index to open (default: 1)')
return parser


def _add_login_subparser(sub):
Expand Down Expand Up @@ -125,7 +145,7 @@ def _add_container_parser(subparsers):
help='Manage containers')
ct_sub = ct_parser.add_subparsers(dest='subcommand')

ct_sub.add_parser('up', help='Start containers')
_add_open_args(ct_sub.add_parser('up', help='Start containers'))
ct_sub.add_parser('down', help='Stop and remove containers')

_add_login_subparser(ct_sub)
Expand Down Expand Up @@ -165,7 +185,7 @@ def _add_project_parser(subparsers):
pj_parser = subparsers.add_parser('project', help='Manage projects (CWD-independent)')
pj_sub = pj_parser.add_subparsers(dest='subcommand')

_add_name_arg(pj_sub.add_parser('up', help='Start containers'))
_add_open_args(_add_name_arg(pj_sub.add_parser('up', help='Start containers')))
_add_name_arg(pj_sub.add_parser('down', help='Stop and remove containers'))

_add_login_subparser(pj_sub)
Expand Down Expand Up @@ -441,7 +461,7 @@ def _add_shortcuts(subparsers):
_add_name_arg(ps_sc)
ps_sc.add_argument('--all', '-a', action='store_true', help='Show all containers')

_add_name_arg(subparsers.add_parser('up', help='Start containers'))
_add_open_args(_add_name_arg(subparsers.add_parser('up', help='Start containers')))
_add_name_arg(subparsers.add_parser('down', help='Stop and remove containers'))

# `[name]` optional + `new_scale` 必須 int の順 (project scale と同じ規則)。
Expand Down
Loading
Loading