# 機能設計書 2-設定管理

## 概要

本ドキュメントは、Decap CMSの設定管理機能の設計を記載する。YAML設定ファイル（config.yml）の読み込み、バリデーション、デフォルト値の適用、正規化処理を行い、CMSの動作を制御する設定データを管理する。

### 本機能の処理概要

本機能は、Decap CMSの動作設定を一元管理する基盤機能である。config.ymlファイルからYAML形式の設定を読み込み、スキーマバリデーション、手動設定とのマージ、ローカルバックエンドの自動検出、設定値の正規化、デフォルト値の適用を経て、最終的なCms設定オブジェクトをReduxストアに格納する。

**業務上の目的・背景**：Decap CMSは多様なバックエンド（GitHub, GitLab, Bitbucket等）やコレクション構成に対応する必要があり、そのすべての設定をconfig.ymlという単一のYAMLファイルで管理する。正しい設定がなければCMSは動作しないため、堅牢な読み込み・バリデーション・デフォルト適用の仕組みが不可欠である。

**機能の利用シーン**：CMSアプリケーション初期化時にbootstrap関数から呼び出される。また、JavaScript API経由で手動設定を渡してCMSを初期化する場合にも利用される。

**主要な処理内容**：
1. 設定ファイルURLの解決（`<link rel="cms-config-url">`またはデフォルト`config.yml`）
2. config.ymlのHTTPフェッチとYAMLパース（`parseConfig`）
3. 手動設定（JavaScript API）とのdeepmerge
4. JSONスキーマによるバリデーション（`validateConfig`）
5. ローカルプロキシサーバーの検出と自動切り替え（`handleLocalBackend`）
6. 非推奨キー（camelCase）のsnake_case変換（`normalizeConfig`）
7. デフォルト値の適用（`applyDefaults`）：publish_mode、slug、public_folder、i18n、コレクション設定等
8. 環境別設定の切り替え（`window.CMS_ENV`）

**関連システム・外部連携**：config.ymlファイルのHTTPサーバー、ローカルプロキシサーバー（decap-server）、各種Gitバックエンド。

**権限による制御**：設定読み込み自体には権限制御はない。ただし、設定内容によって認証方式やバックエンド接続先が決定される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | エラー画面（設定エラー） | 主画面 | config.ymlの読み込みエラー検出と表示。configErrorメソッドによるエラーメッセージとチェック方法の描画 |
| 12 | ローディング画面 | 主画面 | 設定ファイル（config.yml）の読み込み処理中のローディング状態管理 |

## 機能種別

データ連携 / バリデーション / 設定管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| manualConfig | Partial<CmsConfig> | No | JavaScript API経由で渡される手動設定 | validateConfigで検証 |
| onLoad | () => unknown | Yes | 設定読み込み完了後のコールバック関数 | - |
| load_config_file | boolean | No | falseの場合、config.ymlの読み込みをスキップ | - |

### 入力データソース

- config.ymlファイル（HTTPサーバーから取得）
- `<link rel="cms-config-url">`要素（カスタム設定ファイルURL）
- JavaScript APIからの手動設定オブジェクト
- `window.CMS_CONFIG`グローバル変数
- `window.CMS_ENV`グローバル変数（環境切り替え用）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| config | CmsConfig | 正規化・デフォルト適用済みの設定オブジェクト |
| config.backend | object | バックエンド設定（name, repo, branch等） |
| config.collections | CmsCollection[] | コレクション設定の配列 |
| config.publish_mode | CmsPublishMode | 公開モード（simple/editorial_workflow） |
| config.media_folder | string | メディアフォルダパス |
| config.public_folder | string | 公開フォルダパス |
| config.slug | object | スラッグ設定（encoding, clean_accents, sanitize_replacement） |
| config.i18n | CmsI18nConfig | i18n設定（locales, default_locale, structure） |

### 出力先

- Reduxストア（config状態スライス）

## 処理フロー

### 処理シーケンス

```
1. loadConfig(manualConfig, onLoad)が呼び出される
   └─ window.CMS_CONFIGが存在すれば即座にconfigLoadedを返却
2. configLoading()をディスパッチ（isFetching: true）
3. getConfigUrl()で設定ファイルURLを解決
   └─ <link rel="cms-config-url">があればそのhref、なければ'config.yml'
4. getConfigYaml(configUrl, hasManualConfig)で設定ファイルを取得
   └─ fetch()でHTTP GET
   └─ parseConfig()でYAMLパース
   └─ CMS_ENV環境設定のマージ
5. deepmerge(configYaml, manualConfig)で手動設定をマージ
   └─ 手動設定が優先される
6. validateConfig(mergedConfig)でバリデーション
7. handleLocalBackend(mergedConfig)でローカルバックエンド検出
   └─ detectProxyServer()でプロキシサーバーを探索
   └─ 検出された場合、backend.name='proxy'に切り替え
8. normalizeConfig(config)で正規化
   └─ camelCase -> snake_case変換
   └─ sortable_fieldsの正規化
9. applyDefaults(config)でデフォルト値適用
   └─ publish_mode, slug, public_folder等のデフォルト設定
   └─ コレクションごとのi18n設定のマージ
   └─ フォルダ/ファイルコレクションの設定正規化
10. configLoaded(config)をディスパッチ（isFetching: false）
11. onLoad()コールバックを実行
```

### フローチャート

```mermaid
flowchart TD
    A[loadConfig呼び出し] --> B{window.CMS_CONFIG<br/>が存在?}
    B -->|Yes| C[configLoaded即時返却]
    B -->|No| D[configLoading dispatch]
    D --> E[getConfigUrl]
    E --> F{load_config_file<br/>== false?}
    F -->|Yes| G[空オブジェクト使用]
    F -->|No| H[getConfigYaml]
    H --> I{取得成功?}
    I -->|No| J{手動設定<br/>あり?}
    J -->|Yes| K[空オブジェクト使用]
    J -->|No| L[configFailed dispatch]
    I -->|Yes| M[parseConfig]
    G --> N[deepmerge]
    K --> N
    M --> N
    N --> O[validateConfig]
    O --> P[handleLocalBackend]
    P --> Q{プロキシ<br/>検出?}
    Q -->|Yes| R[backend.name=proxy に切替]
    Q -->|No| S[設定変更なし]
    R --> T[normalizeConfig]
    S --> T
    T --> U[applyDefaults]
    U --> V[configLoaded dispatch]
    V --> W[onLoadコールバック実行]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 手動設定優先 | deepmergeで手動設定がconfig.ymlの内容を上書きする | 手動設定が提供された場合 |
| BR-02 | デフォルトpublish_mode | publish_modeが未指定の場合はsimple（直接公開）がデフォルト | publish_mode未設定時 |
| BR-03 | public_folder自動設定 | public_folderが未設定の場合、media_folderの先頭に`/`を付加した値がデフォルト | public_folder未設定時 |
| BR-04 | slugデフォルト | slug.encoding='unicode', slug.clean_accents=false, slug.sanitize_replacement='-' | 各値が未設定時 |
| BR-05 | i18nデフォルトロケール | i18n.default_localeが未設定の場合、locales配列の最初の要素がデフォルト | i18n有効時 |
| BR-06 | ローカルバックエンド自動切替 | local_backendが有効でプロキシサーバーが検出された場合、backend.nameを'proxy'に切り替え | localhost/127.0.0.1上で動作時 |
| BR-07 | 非推奨キー変換 | camelCaseのフィールドキー（dateFormat等）を自動的にsnake_case（date_format等）に変換し警告出力 | 非推奨キーが使用されている場合 |
| BR-08 | ファイルコレクションi18n制限 | ファイルベースコレクションのi18n構造はsingle_fileのみ許可 | ファイルコレクションでi18n有効時 |

### 計算ロジック

特になし。

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

### 操作別データベース影響一覧

本機能はクライアントサイド処理のため、直接的なデータベース操作はない。

| 操作 | 対象ストア | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 設定読み込み開始 | config | UPDATE | CONFIG_REQUESTでisFetching: true |
| 設定読み込み成功 | config | UPDATE | CONFIG_SUCCESSで設定全体を格納 |
| 設定読み込み失敗 | config | UPDATE | CONFIG_FAILUREでエラー情報を格納 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| CONFIG_FAILURE | ファイル取得エラー | config.ymlのHTTPリクエストが失敗（404等） | 手動設定がある場合は空オブジェクトで続行、ない場合はエラー画面表示 |
| CONFIG_FAILURE | Content-Typeエラー | レスポンスのContent-TypeにYAMLが含まれない | 手動設定がある場合は空オブジェクトで続行 |
| CONFIG_FAILURE | バリデーションエラー | validateConfigでスキーマ不一致検出 | エラー画面にバリデーションエラー詳細を表示 |
| Error | i18nロケール不整合 | default_localeがlocales配列に含まれない | throwでエラーを投げる |
| Error | i18n構造不正 | ファイルコレクションでsingle_file以外のi18n構造 | throwでエラーを投げる |

### リトライ仕様

リトライ機構は実装されていない。ページリロードで再試行。

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

クライアントサイド処理のためトランザクション管理は不要。

## パフォーマンス要件

- config.ymlの読み込みは単一のHTTPリクエストで完了すること
- ローカルプロキシサーバーの検出はネットワークタイムアウトに依存（fetch API標準）

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

- config.ymlの取得は`credentials: 'same-origin'`で実行
- ローカルバックエンドの検出はlocalhost/127.0.0.1および`allowed_hosts`で指定されたホストに限定
- config.ymlにはリポジトリのアクセストークン等の機密情報を含めるべきではない

## 備考

- YAMLパーサーとして`yaml`パッケージを使用（`maxAliasCount: -1`でエイリアス無制限、`prettyErrors: true`でエラーメッセージ整形、`merge: true`でYAMLマージキーサポート）
- `immer`の`produce`関数によるイミュータブルな設定更新
- WIDGET_KEY_MAPに定義された非推奨キー: dateFormat, timeFormat, pickerUtc, editorComponents, valueType, valueField, searchFields, displayFields, optionsLength

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types/redux.ts | `packages/decap-cms-core/src/types/redux.ts` | CmsConfig, CmsCollection, CmsField, CmsI18nConfig等の型定義 |
| 1-2 | config.ts (reducer) | `packages/decap-cms-core/src/reducers/config.ts` | デフォルト状態（isFetching: true）とリデューサーの状態遷移 |

**読解のコツ**: CmsConfigの型定義が設定ファイルの全体構造を表す。immerのproduceパターンで状態更新しているため、見た目はmutableだが実際はimmutableな更新が行われている。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | config.ts (actions) | `packages/decap-cms-core/src/actions/config.ts` | loadConfig関数 - 設定管理のメインエントリーポイント |

**主要処理フロー**:
1. **523-526行目**: window.CMS_CONFIGチェックと即時返却
2. **527-528行目**: thunkアクション定義、configLoading dispatch
3. **531行目**: getConfigUrl()の呼び出し
4. **532-536行目**: load_config_fileフラグとgetConfigYaml
5. **539行目**: deepmergeによるマージ
6. **541行目**: validateConfigバリデーション
7. **543行目**: handleLocalBackendローカルバックエンド処理
8. **544行目**: normalizeConfig正規化
9. **546行目**: applyDefaultsデフォルト適用
10. **548行目**: configLoaded dispatch

#### Step 3: YAML解析と設定URL解決を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | config.ts (actions) | `packages/decap-cms-core/src/actions/config.ts` | getConfigUrl, getConfigYaml, parseConfig関数 |

**主要処理フロー**:
- **62-73行目**: getConfigUrl - `<link rel="cms-config-url">`からURLを取得、デフォルトは'config.yml'
- **403-421行目**: getConfigYaml - fetchでファイル取得、Content-Type検証、parseConfig呼び出し
- **388-401行目**: parseConfig - yaml.parseでYAML解析、CMS_ENVによる環境別設定マージ

#### Step 4: 設定正規化とデフォルト適用を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | config.ts (actions) | `packages/decap-cms-core/src/actions/config.ts` | normalizeConfig, applyDefaults関数 |

**主要処理フロー**:
- **188-229行目**: normalizeConfig - 非推奨キーのsnake_case変換、sortable_fieldsの正規化
- **231-386行目**: applyDefaults - publish_mode, slug, public_folder, i18n, コレクション設定のデフォルト適用
- **95-109行目**: setSnakeCaseConfig - camelCase -> snake_case変換と警告出力

#### Step 5: ローカルバックエンド検出を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | config.ts (actions) | `packages/decap-cms-core/src/actions/config.ts` | handleLocalBackend, detectProxyServer関数 |

**主要処理フロー**:
- **444-484行目**: detectProxyServer - localhost/127.0.0.1チェック、プロキシサーバーへのPOSTリクエスト、repo/publish_modes/type情報の取得
- **498-521行目**: handleLocalBackend - プロキシ検出時にbackend.name='proxy'とproxy_urlを設定

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

```
loadConfig(manualConfig, onLoad)
    |
    +-- configLoading()
    +-- getConfigUrl()
    |       +-- document.querySelector('link[rel="cms-config-url"]')
    +-- getConfigYaml(configUrl, hasManualConfig)
    |       +-- fetch(file, {credentials: 'same-origin'})
    |       +-- parseConfig(text)
    |               +-- yaml.parse(data)
    +-- deepmerge(configYaml, manualConfig)
    +-- validateConfig(mergedConfig)
    +-- handleLocalBackend(mergedConfig)
    |       +-- detectProxyServer(local_backend)
    |               +-- fetch(proxyUrl, {method: 'POST'})
    +-- normalizeConfig(config)
    |       +-- traverseFieldsJS(fields, setSnakeCaseConfig)
    |       +-- normalizeSortableFields()
    +-- applyDefaults(config)
    |       +-- produce(config, ...)
    |       +-- resolveBackend(config)
    |       +-- getI18nDefaults()
    |       +-- setI18nDefaultsForFields()
    |       +-- traverseFieldsJS(fields, setDefaultPublicFolderForField)
    |       +-- selectDefaultSortableFields()
    +-- configLoaded(config)
    +-- onLoad()
```

### データフロー図

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

config.yml (HTTP GET) --------> getConfigYaml() --+
                                     |             |
                                parseConfig() -----+
                                                    |
manualConfig (JS API) ----------> deepmerge() ------+---> mergedConfig
                                                              |
                                                    validateConfig()
                                                              |
local_backend設定 ----------> handleLocalBackend() ----> 設定にproxy反映
                                                              |
                                                    normalizeConfig()
                                                              |
                                                    applyDefaults()
                                                              |
                                                    configLoaded() --> Reduxストア
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| config.ts (actions) | `packages/decap-cms-core/src/actions/config.ts` | ソース | 設定管理のメインロジック（loadConfig, normalizeConfig, applyDefaults等） |
| config.ts (reducer) | `packages/decap-cms-core/src/reducers/config.ts` | ソース | 設定状態のリデューサー |
| configSchema.ts | `packages/decap-cms-core/src/constants/configSchema.ts` | ソース | JSONスキーマによるバリデーション |
| i18n.ts | `packages/decap-cms-core/src/lib/i18n.ts` | ソース | i18n設定の定数と型定義 |
| collectionTypes.ts | `packages/decap-cms-core/src/constants/collectionTypes.ts` | ソース | コレクションタイプ定数（FILES, FOLDER） |
| publishModes.ts | `packages/decap-cms-core/src/constants/publishModes.ts` | ソース | 公開モード定数（SIMPLE, EDITORIAL_WORKFLOW） |
| backend.ts | `packages/decap-cms-core/src/backend.ts` | ソース | resolveBackend関数（バックエンド解決） |
| collections.ts (reducer) | `packages/decap-cms-core/src/reducers/collections.ts` | ソース | selectDefaultSortableFields関数 |
| integrations.ts (reducer) | `packages/decap-cms-core/src/reducers/integrations.ts` | ソース | getIntegrations, selectIntegration関数 |
