# 帳票設計書 4-YAML Frontmatter Markdownファイル出力

## 概要

本ドキュメントは、Decap CMSにおけるYAML Frontmatter付きMarkdownファイル出力機能の設計を記述する。FrontmatterFormatterクラスのtoFileメソッドにより、メタデータ部分を`---`区切りのYAML形式で、本文をMarkdownとして結合出力する仕組みを定義する。

### 本帳票の処理概要

本帳票は、CMSのコンテンツエディタで編集されたエントリデータを、YAML Frontmatter付きMarkdownファイルとして出力する機能を定義する。

**業務上の目的・背景**：YAML Frontmatter付きMarkdownは、Jekyll、Hugo、Gatsby、Next.js等の静的サイトジェネレータで最も広く採用されるコンテンツ形式である。メタデータ（タイトル、日付、カテゴリ等）とMarkdown本文を1つのファイルに統合でき、コンテンツ管理の標準的な手法として定着している。Decap CMSのデフォルトフォーマットでもあり、format指定がない場合のフォールバックとしても使用される。

**帳票の利用シーン**：コレクション設定でformat: yaml-frontmatterが指定されている場合、またはファイル拡張子が.mdの場合に使用される。ブログ記事、ドキュメントページ等、本文とメタデータの両方を持つコンテンツの管理に利用される。format指定なしの場合もデフォルトとしてFrontmatter形式が選択される。

**主要な出力内容**：
1. `---`区切りのYAML Frontmatter部分（メタデータフィールド）
2. Markdown本文（bodyフィールド）
3. sortedKeysによるメタデータキーのソート順制御
4. commentsによるメタデータキーへのコメント付与

**帳票の出力タイミング**：エディタ画面で「Save」ボタンを押下した際、resolveFormatで決定されたフォーマッタのtoFileメソッドが呼び出され、Frontmatter付きMarkdown文字列が生成される。

**帳票の利用者**：コンテンツ編集者（CMS利用者）、静的サイトジェネレータのビルドシステム

## 帳票種別

データファイル出力（YAML Frontmatter付きMarkdownファイル）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | エディタ画面 | /collections/{collection}/entries/{slug} | Saveボタン押下 |
| - | 新規エントリ作成画面 | /collections/{collection}/new | Saveボタン押下 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | Markdown (.md) |
| 用紙サイズ | N/A（テキストファイル） |
| 向き | N/A |
| ファイル名 | コレクション設定のslugテンプレートに基づく |
| 出力方法 | Gitリポジトリへのコミット（バックエンドAPI経由） |
| 文字コード | UTF-8 |

### PDF固有設定

N/A（テキストファイル出力のため該当なし）

### Excel固有設定

N/A（テキストファイル出力のため該当なし）

## 帳票レイアウト

### レイアウト概要

YAML Frontmatter部分とMarkdown本文部分が`---`デリミタで区切られたファイルとして出力される。

```
┌─────────────────────────────────────┐
│ ---                                 │
│ title: "記事タイトル"                │
│ date: 2024-01-01                    │
│ tags:                               │
│   - tag1                            │
│   - tag2                            │
│ ---                                 │
├─────────────────────────────────────┤
│                                     │
│ # 見出し                            │
│                                     │
│ 本文のMarkdownコンテンツ             │
│                                     │
└─────────────────────────────────────┘
```

### ヘッダー部（Frontmatter）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | 開始デリミタ | Frontmatter開始マーカー | 固定値 `---` | --- |
| 2 | メタデータ | body以外の全フィールドデータ | entry.data（bodyを除く） | YAML形式 |
| 3 | 終了デリミタ | Frontmatter終了マーカー | 固定値 `---` | --- |

### 明細部（本文）

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | 本文 | Markdownコンテンツ | entry.data.body | Markdown記法 | N/A |

### フッター部

N/A

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| エントリデータ | エディタで入力された全フィールドデータ | Yes |
| ソートキー指定 | sortedKeysパラメータで指定されたキー順序 | No |
| コメント指定 | commentsパラメータで指定されたキー別コメント | No |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | sortedKeysで指定された順序 | 指定順（YAML parsers経由） |

### 改ページ条件

N/A（テキストファイルのため該当なし）

## データベース参照仕様

### 参照テーブル一覧

N/A（Gitリポジトリベース）

### テーブル別参照項目詳細

N/A

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| body分離 | `const { body = '', ...meta } = data` | N/A | dataオブジェクトからbodyを分離し、残りをメタデータとして扱う |
| 末尾改行トリム | `body.slice(-1) !== '\n'`の場合、生成文字列の末尾改行を除去 | N/A | gray-matterが常に末尾改行を付加する問題への対策 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[Save操作] --> B[resolveFormat でフォーマッタ決定]
    B --> C[FrontmatterFormatter.toFile 呼び出し]
    C --> D[bodyとmetaを分離]
    D --> E{formatが設定済か?}
    E -->|Yes| F[設定済みformat使用]
    E -->|No| G[デフォルトYAML format使用]
    F --> H[matter.stringify でFrontmatter生成]
    G --> H
    H --> I{元bodyが改行で終わるか?}
    I -->|Yes| J[そのまま返却]
    I -->|No| K[末尾改行を除去して返却]
    J --> L[バックエンドAPIでGitコミット]
    K --> L
    L --> M[終了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| パースエラー | Frontmatter形式が不正な場合 | gray-matterのエラーメッセージ | Frontmatterの構文を確認 |
| YAMLシリアライズエラー | メタデータに不正な値が含まれる場合 | yamlFormatterのエラーメッセージ | メタデータの型・構造を確認 |
| バックエンドエラー | Gitコミットに失敗した場合 | バックエンド固有のエラーメッセージ | ネットワーク接続・認証情報を確認 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 1エントリ（メタデータ + 本文） |
| 目標出力時間 | 数ミリ秒（matter.stringify処理） |
| 同時出力数上限 | 1件（エディタ操作は逐次処理） |

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

- Frontmatter付きMarkdownファイルはGitリポジトリに保存されるため、リポジトリのアクセス制御に依存する
- gray-matterライブラリによるパース時、意図しないコード実行のリスクはcustomEngines（parsers）の使用により軽減されている

## 備考

- gray-matterライブラリを使用してFrontmatter生成・パースを行う
- gray-matterは常に末尾に改行を付加するため（https://github.com/jonschlinkert/gray-matter/issues/96）、元のbodyが改行で終わらない場合にトリム処理を行う
- format未指定の場合はデフォルトでYAML形式が使用される（frontmatter.ts 120行目）
- カスタムデリミタの指定が可能（frontmatterYAML関数のcustomDelimiter引数）
- FrontmatterInferインスタンスは自動推論モードで、ファイル先頭の`---`/`+++`/`{`からフォーマットを判定する

---

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

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

### 推奨読解順序

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

Frontmatter出力に関わるデータ型とインターフェースを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | frontmatter.ts | `packages/decap-cms-core/src/formats/frontmatter.ts` | Language型（yaml/toml/json）、Format型（language + delimiters）、Delimiter型（string | [string, string]）を確認 |
| 1-2 | frontmatter.ts | `packages/decap-cms-core/src/formats/frontmatter.ts` | parsersオブジェクト（18-54行目）でYAML/TOML/JSONのparse/stringifyラッパーを確認 |

**読解のコツ**: frontmatter.tsは3つのFrontmatter形式（YAML/TOML/JSON）を統合的に扱うモジュールであり、parsersオブジェクトがgray-matterのenginesオプションとして渡される。

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

FrontmatterFormatterクラスのインスタンス化とフォーマット解決の流れを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | frontmatter.ts | `packages/decap-cms-core/src/formats/frontmatter.ts` | frontmatterYAML関数（140-142行目）でYAML形式のFrontmatterFormatterを生成する流れを確認 |
| 2-2 | formats.ts | `packages/decap-cms-core/src/formats/formats.ts` | formatByNameで`yaml-frontmatter`キーに対してfrontmatterYAML()が呼び出される流れを確認（51行目） |

**主要処理フロー**:
1. **140行目**: `frontmatterYAML(customDelimiter)` でFrontmatterFormatterインスタンスを生成
2. **97-99行目**: コンストラクタでgetFormatOpts(Languages.YAML, customDelimiter)を呼び出し、format = {language: 'yaml', delimiters: '---'}を設定

#### Step 3: toFile処理を理解する

Frontmatter付きMarkdown文字列の生成処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | frontmatter.ts | `packages/decap-cms-core/src/formats/frontmatter.ts` | toFileメソッド（112-135行目）の処理フローを理解する |

**主要処理フロー**:
- **117行目**: `const { body = '', ...meta } = data` でbodyとメタデータを分離
- **120行目**: format未設定時はデフォルトでYAML形式を使用
- **125行目**: 元のbodyが改行で終わらない場合のフラグを設定
- **126-133行目**: `matter.stringify(body, meta, { engines: parsers, sortedKeys, comments, ...format })` でFrontmatter文字列を生成
- **134行目**: 末尾改行のトリム処理

#### Step 4: fromFile処理を理解する

パース処理の流れも理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | frontmatter.ts | `packages/decap-cms-core/src/formats/frontmatter.ts` | fromFileメソッド（101-110行目）でgray-matterによるパースとbodyの条件付き追加を確認 |

**主要処理フロー**:
- **102行目**: format未設定時はinferFrontmatterFormat関数でフォーマットを自動推論
- **103行目**: `matter(content, { engines: parsers, ...format })` でパース
- **106-109行目**: パース結果のdataとcontent（body）を結合。bodyが空でない場合のみbodyキーを追加

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

```
resolveFormat (formats.ts:66)
    |
    +-- formatByName (formats.ts:42)
            |
            +-- frontmatterYAML (frontmatter.ts:140)
                    |
                    +-- new FrontmatterFormatter('yaml', customDelimiter)
                            |
                            +-- getFormatOpts (frontmatter.ts:75)
                            |
                            +-- toFile (frontmatter.ts:112)
                                    |
                                    +-- matter.stringify (gray-matter library)
                                    |       +-- parsers.yaml.stringify
                                    |               +-- yamlFormatter.toFile (yaml.ts:47)
                                    +-- 末尾改行トリム処理
```

### データフロー図

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

entry.data ─────────────┐
  |                     |
  +-- body (string) ----+-> matter.stringify --------> Frontmatter付き
  |                     |     |                        Markdown文字列
  +-- meta (残り) ------+     +-- parsers.yaml         (.md)
                              |     +-- yamlFormatter
sortedKeys (string[]) --------+
comments (Record) ------------+
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| frontmatter.ts | `packages/decap-cms-core/src/formats/frontmatter.ts` | ソース | FrontmatterFormatterクラス、parsersオブジェクト、inferFrontmatterFormat関数 |
| yaml.ts | `packages/decap-cms-core/src/formats/yaml.ts` | ソース | YAMLフォーマッタ（parsers.yaml.stringify経由で呼び出し） |
| formats.ts | `packages/decap-cms-core/src/formats/formats.ts` | ソース | フォーマット解決・フォーマッタ選択ロジック |
| helpers.ts | `packages/decap-cms-core/src/formats/helpers.ts` | ソース | sortKeysユーティリティ（yamlFormatter.toFile経由で使用） |
