# 機能設計書 45-ローカルバックエンド自動検出

## 概要

本ドキュメントは、Decap CMSのローカルバックエンド自動検出機能の設計を記述する。ローカル環境でDecap CMS Proxy Serverの存在を自動検出し、バックエンドをプロキシバックエンドに自動切り替えする機能である。

### 本機能の処理概要

**業務上の目的・背景**：開発者がローカル環境でDecap CMSを使用する際、config.ymlにプロキシバックエンドの設定を手動で記述する手間を省きたい。本機能により、config.ymlに`local_backend: true`を追加するだけで、ローカルで動作するDecap Serverを自動検出し、バックエンド設定を自動的にプロキシモードに切り替える。

**機能の利用シーン**：ローカル開発サーバー（localhost/127.0.0.1）でCMSを実行し、`local_backend: true`（または`local_backend`オブジェクト）が設定されている場合に、CMS初期化時に自動実行される。

**主要な処理内容**：
1. `local_backend`設定の有無と現在のホスト名の検証
2. Decap CMS Proxy Serverへのinfo APIリクエスト送信
3. サーバーレスポンスの検証（repo、publish_modes、typeフィールド）
4. 検出成功時にバックエンド設定を`proxy`に自動切り替え
5. publish_modeの互換性チェックと自動調整

**関連システム・外部連携**：Decap Server（No.46）のinfo APIエンドポイント

**権限による制御**：ホスト名ベースの制限がある。デフォルトではlocalhostと127.0.0.1のみ許可。`local_backend.allowed_hosts`で追加ホストを指定可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 初期化時 | バックグラウンド | アプリケーション初期化時に自動実行される |

## 機能種別

設定管理 / 自動検出

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| config.local_backend | boolean / CmsLocalBackend | No | ローカルバックエンド設定 | true または設定オブジェクト |
| config.local_backend.url | string | No | プロキシサーバーURL | デフォルト: http://localhost:8081/api/v1 |
| config.local_backend.allowed_hosts | string[] | No | 許可するホスト名リスト | デフォルト: ['localhost', '127.0.0.1'] |
| config.publish_mode | CmsPublishMode | No | 公開モード | - |

### 入力データソース

- CMS設定ファイル（config.yml）: local_backend設定
- ブラウザ: location.hostname
- Decap Server: info APIレスポンス

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| proxyUrl | string | 検出されたプロキシサーバーURL（未検出時は空） |
| publish_modes | CmsPublishMode[] | サーバーがサポートする公開モード |
| type | string | バックエンドタイプ（例: 'local_git', 'local_fs'） |

### 出力先

- CMS設定オブジェクト: backend.name='proxy'、backend.proxy_url=検出URL、publish_mode調整

## 処理フロー

### 処理シーケンス

```
1. detectProxyServer()呼び出し
   └─ allowedHostsにlocation.hostnameが含まれるか確認
   └─ 含まれない場合またはlocal_backendがfalsyの場合は空オブジェクトを返却
2. プロキシURL決定
   └─ local_backend === true: 'http://localhost:8081/api/v1'
   └─ local_backend.url指定あり: 指定URL
   └─ local_backend.url未指定: デフォルトURLのlocalhostをlocation.hostnameに置換
3. info APIリクエスト
   └─ POST { action: 'info' } をプロキシURLに送信
4. レスポンス検証
   └─ repo(string)、publish_modes(Array)、type(string)の型チェック
   └─ 全て有効ならプロキシ検出成功
5. handleLocalBackend()
   └─ 検出成功時: config.backend.name='proxy'、config.backend.proxy_url=proxyUrl
   └─ publish_modeの互換性チェック・調整
```

### フローチャート

```mermaid
flowchart TD
    A[handleLocalBackend] --> B{local_backend設定あり?}
    B -->|No| C[元の設定を返却]
    B -->|Yes| D[detectProxyServer]
    D --> E{allowedHostsに含まれる?}
    E -->|No| F[空オブジェクト返却]
    E -->|Yes| G[プロキシURL決定]
    G --> H[POST /api/v1 info]
    H --> I{レスポンス有効?}
    I -->|No| J[空オブジェクト返却]
    I -->|Yes| K[proxyUrl, publish_modes, type返却]
    F --> C
    J --> C
    K --> L{proxyUrl存在?}
    L -->|No| C
    L -->|Yes| M[backend.name = proxy]
    M --> N[backend.proxy_url = proxyUrl]
    N --> O[publish_mode調整]
    O --> P[変更後の設定を返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-45-01 | ホスト制限 | デフォルトでlocalhostと127.0.0.1のみ許可 | detectProxyServer呼び出し時 |
| BR-45-02 | デフォルトURL | `http://localhost:8081/api/v1` | local_backend === true時 |
| BR-45-03 | ホスト名置換 | local_backend.url未指定時、デフォルトURLのlocalhostをlocation.hostnameに置換 | local_backend がオブジェクト時 |
| BR-45-04 | レスポンス検証 | repo(string)、publish_modes(Array)、type(string)が全て有効であること | info APIレスポンス受信時 |
| BR-45-05 | publish_mode互換性 | サーバーがサポートしないpublish_modeが設定されている場合、サーバーの最初のモードに切り替え | handleLocalBackend内 |
| BR-45-06 | 失敗時フォールバック | プロキシサーバーが検出できない場合は元の設定を変更しない | 全体 |

### 計算ロジック

- プロキシURL算出: `local_backend.url || defaultUrl.replace('localhost', location.hostname)`

## データベース操作仕様

本機能はデータベースを操作しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ネットワークエラー | プロキシサーバーに接続できない | catchで捕捉しコンソールログ出力。元の設定を使用 |
| - | 不正レスポンス | レスポンスにrepo/publish_modes/typeが含まれない | コンソールログ出力。元の設定を使用 |

### リトライ仕様

リトライは行わない。初期化時に一度だけ検出を試みる。

## トランザクション仕様

設定オブジェクトの変更はimmerのproduceで不変更新される。

## パフォーマンス要件

CMS初期化時に1回のみ実行される。ネットワークリクエストは1回のみ。

## セキュリティ考慮事項

- ホスト名制限により、本番環境でのプロキシサーバー検出を防止
- allowed_hostsで許可ホストを明示的に指定可能
- プロキシサーバーへの通信はHTTP（ローカル通信のため）

## 備考

- `detectProxyServer()`と`handleLocalBackend()`の2つの関数で構成される
- `getPublishMode()`は公開モードの互換性チェックを行うヘルパー関数
- config.tsの`loadConfig()`関数内で`handleLocalBackend()`が呼ばれる（543行目）

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | config.ts | `packages/decap-cms-core/src/actions/config.ts` | CmsLocalBackend型（28行目のインポート）、CmsPublishMode型の理解 |

**読解のコツ**: `CmsLocalBackend`型はredux.tsの型定義ファイルで定義されている。`url`、`allowed_hosts`プロパティを持つオブジェクト型。`local_backend`設定は`boolean`またはこのオブジェクト型のいずれかを受け付ける。

#### Step 2: プロキシサーバー検出処理

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | config.ts | `packages/decap-cms-core/src/actions/config.ts` | detectProxyServer関数（444-484行目） |

**主要処理フロー**:
- **445-448行目**: allowedHostsの構築（デフォルト: localhost, 127.0.0.1 + local_backend.allowed_hosts）
- **451-453行目**: ホスト名チェックとlocal_backendのfalsyチェック
- **455-459行目**: プロキシURL決定ロジック
- **462-467行目**: info APIへのPOSTリクエスト送信
- **468-476行目**: レスポンスの型検証とプロキシ情報返却
- **480-483行目**: エラー時のフォールバック（空オブジェクト返却）

#### Step 3: ローカルバックエンドハンドリング

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | config.ts | `packages/decap-cms-core/src/actions/config.ts` | handleLocalBackend関数（498-521行目）とgetPublishMode関数（486-496行目） |

**主要処理フロー**:
- **498-501行目**: local_backend未設定時は元の設定をそのまま返却
- **503-507行目**: detectProxyServerの呼び出し
- **509-511行目**: proxyUrl未検出時は元の設定をそのまま返却
- **513-520行目**: immer produceで設定をイミュータブルに更新（backend.name='proxy', backend.proxy_url=proxyUrl, publish_mode調整）

#### Step 4: 設定読み込みフローでの呼び出し

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | config.ts | `packages/decap-cms-core/src/actions/config.ts` | loadConfig関数（523-558行目）内でhandleLocalBackendが呼ばれる箇所（543行目） |

**主要処理フロー**:
- **543行目**: `const withLocalBackend = await handleLocalBackend(mergedConfig);`
- **544行目**: `const normalizedConfig = normalizeConfig(withLocalBackend);`
- これにより、YAML設定読み込み→マニュアル設定マージ→ローカルバックエンド検出→正規化→デフォルト適用の順で処理される

### プログラム呼び出し階層図

```
loadConfig(manualConfig, onLoad)
    |
    +-- handleLocalBackend(mergedConfig)
    |       |
    |       +-- detectProxyServer(config.local_backend)
    |       |       +-- fetch(proxyUrl, { action: 'info' })
    |       |       +-- レスポンス検証
    |       |
    |       +-- [検出成功時]
    |       |       +-- produce(config)
    |       |       +-- config.backend.name = 'proxy'
    |       |       +-- config.backend.proxy_url = proxyUrl
    |       |       +-- getPublishMode(config, publishModes, backendType)
    |       |
    |       +-- [検出失敗時]
    |               +-- 元の設定を返却
    |
    +-- normalizeConfig(withLocalBackend)
    +-- applyDefaults(normalizedConfig)
```

### データフロー図

```
[入力]                      [処理]                          [出力]

config.yml         ──▶  handleLocalBackend()       ──▶  変更後config
(local_backend設定)       detectProxyServer()             (backend.name='proxy')

location.hostname  ──▶  allowedHostsチェック       ──▶  検出可否判定
(ブラウザ情報)             ホスト名検証                    (true/false)

Decap Server       ──▶  info APIレスポンス         ──▶  proxyUrl, type
(/api/v1)                 repo/publish_modes/type          publish_modes
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| config.ts | `packages/decap-cms-core/src/actions/config.ts` | ソース | detectProxyServer、handleLocalBackend、loadConfig関数 |
