## 概要

zudo-docは完全な静的サイトジェネレーターです。`pnpm build`の出力はHTML、CSS、JavaScriptファイルのディレクトリであり、どこでもデプロイできます。デフォルトのデプロイ先は**Cloudflare Workers**（静的アセット）で、CI/CDの自動化にはGitHub Actionsを使用します。

このguideはzudo-doc **showcase repository**のhost-owned Workerとworkflowを説明します。
fresh downstream scaffoldには、これらworkflow file、live AI handler、`worker-entry.ts`、
`AiChatDailySpendCap`は含まれません。host側の部品を追加するときの参照として利用してください。

CIパイプラインは合計ビルド時間を短縮するために並列ジョブに分割されています。サイト生成とドキュメント履歴の抽出は独立したジョブとして実行され、デプロイ前にマージされます。

## Cloudflare Workers静的アセット

ビルドされたサイトは`wrangler deploy`を使用してデプロイされます。デプロイアーティファクトには以下が含まれます：

- zfbのビルド出力（`dist/`）
- 事前生成されたdocヒストリーJSONファイル（`docHistory`が有効な場合）
- `worker-entry.ts` — source-ownedのWrangler entry。生成された`dist/_worker.js`をimportし、`dist/_zfb_inner.mjs`を維持します

サイトはWorkers静的アセット層によりルート（`base = "/"`）で配信されます。動的ルート（例：`/api/ai-chat`）は`dist/_worker.js`が処理し、それ以外のGETリクエストは`dist/`から静的アセットとして配信されます。

[Dynamic content unavailable in text export]

サイトURLは`wrangler.toml`の`[[routes]]`でカスタムドメインバインディングとして設定します。
ライブAIチャットでは`ANTHROPIC_API_KEY`シークレット、`DOCS_SITE_URL`、ソフトなIPごとの
`RATE_LIMIT` KV、そしてexport class `AiChatDailySpendCap`へバインドした
`AI_CHAT_DAILY_SPEND_CAP`名前空間も使います。Worker migrationは
`v1-ai-chat-daily-spend-cap`と`new_sqlite_classes = ["AiChatDailySpendCap"]`で、
本番の`wrangler deploy`が適用します。

デプロイentryは生成されたアダプターだけでなく、カスタム`worker-entry.ts`です。Wranglerより前に
ビルドして`dist/_worker.js`と`dist/_zfb_inner.mjs`を用意し、カスタムentryがアダプターハンドラーと
Durable Object exportを維持します。PR CIは同じビルドartifactを復元し、Workers-runtime SQLite testと
Wrangler dry-runをその同一ファイルに対して実行します。



## CIパイプラインの構造

プロジェクトでは3つのGitHub Actionsワークフローを使用します：本番デプロイ（`main-deploy.yml`）、PRチェックとプレビュー（`pr-checks.yml`）、ブランチプレビュー（`preview-deploy.yml`）。

### 本番環境（`main-deploy.yml`）

`main`へのプッシュでトリガーされます。5つのジョブが実行されます：

1. **build-site** — フルクローン（`fetch-depth: 0`）、`pnpm build`でzfbサイトをビルド（`SKIP_DOC_HISTORY`なし — preBuildステップがSSGメタブロックを生成するためにフルgit履歴が必要）
2. **html-validate** — ビルド済み`dist/`のHTMLエレメントエラーを検証
3. **build-history** — フルクローン（`fetch-depth: 0`）、`@takazudo/zudo-doc-history-server generate`を実行してすべてのコンテンツファイルのgit履歴を事前抽出
4. **deploy** — サイトと履歴のアーティファクトをマージし、`wrangler deploy`で本番環境にデプロイ
5. **notify** — デプロイステータスをIFTTTウェブフックで通知

**コンカレンシー:** `group: production-deploy, cancel-in-progress: false` — 先行するデプロイはキャンセルされずに完了が許可されます。

### PRチェック（`pr-checks.yml`）

`main`を対象としたプルリクエストでトリガーされます。20のジョブが実行されます：

1. **check-template-drift** — ソースファイルとジェネレーターテンプレート間のドリフトを検証
2. **check-no-host-alias-in-package** — package sourceからhost専用import aliasを拒否
3. **check-pin-parity** — `@takazudo/zfb`のピンがすべてのパッケージソース間で一致しているか確認
4. **check-fixture-settings-drift** — `src/config/settings.ts`のすべてのキーがe2eフィクスチャー設定ファイルに存在するか（またはアローリスト済みか）を確認
5. **check-package-wind-manifest** — 公開 `wind.json` マニフェストと authored class のカバレッジを検証
6. **check-b4push-ci-parity** — `scripts/run-b4push.sh`の軽量ガードゲートすべてに対応するCIジョブが存在するか確認
7. **check-e2e-spec-naming** — すべてのe2eスペックファイル名が既知のフィクスチャープレフィックスを使用しているか確認（Playwrightが確実に検出できるよう）
8. **check-flaky-tracking-issue** — flaky/local-only testにtracking issueを必須化
9. **check-wait-debt** — E2E testの注釈なし固定waitを拒否
10. **check-component-tokens** — 生成されたcomponent-token CSSが最新か確認
11. **lint-gates** — tag、compatibility、resolution、design-token guardを実行
12. **typecheck** — rootとworkspaceのTypeScript checkを実行
13. **worker-contract** — `build-site` artifactを復元し、Workers-runtime Durable Object testとWrangler dry-runを実行
14. **package-tests** — すべてのパッケージスコープのvitestスイートを実行
15. **root-tests** — ルートユニットテストスイートを実行
16. **build-site** — フルクローン（`fetch-depth: 0`）、`pnpm build`、その後`check:links`（`SKIP_DOC_HISTORY`なし — 本番と同じ理由：preBuildにSSGメタ日付のためのフルgit履歴が必要）
17. **html-validate** — ビルド済み`dist/`のHTMLエレメントエラーを検証
18. **build-history** — フルクローン、docヒストリーの生成
19. **e2e** — Playwrightエンドツーエンドテストを実行
20. **preview** — アーティファクトをマージして`wrangler versions upload --preview-alias`でプレビューをアップロード、PRコメントとしてURLを投稿

**コンカレンシー:** `group: pr-checks-{pr-number}, cancel-in-progress: true` — 古い実行はCIの消費を節約するためにキャンセルされます。

### プレビューデプロイ

各PRは`wrangler versions upload --preview-alias <alias>`により**安定したプレビューエイリアス**URLを取得します。生成されるアダプター専用preview configは`preview_urls = true`を維持し、本番と同様にprimary `workers_dev` routeは無効のままにします。エイリアスはPRチェックでは`pr-<番号>`、ブランチプレビューではブランチスラッグです。URLは`*.workers.dev`上にあり（本番のカスタムドメインではありません）、サブドメインはエイリアスで始まります：

```
https://<alias>-zudo-doc-preview.<your-subdomain>.workers.dev
```

CIはwranglerの出力から正確なホストを解析し、**preview**ジョブによってPRのコメントとして自動的に投稿されます。
分離されたserviceを初めて作成するときは、CIがCloudflare APIを介してpreview URLのsubdomain設定だけを明示的に有効化し、alias付きuploadを再試行します。
preview entryはGET/HEADを内部hostnameで`ASSETS`へ直接送り、その他のmethodを生成されたadapterへ委譲します。Cloudflareのasset bindingはpathnameだけでmatchするため、API POST smoke gateを維持しながらpreview aliasがsame-zone Worker fetchとして扱われることを避けられます。本番は生成されたadapterを引き続き直接exportします。

Durable Objectsを実装するversionにはCloudflareがプレビューURLを発行しないため、プレビューworkflowは
サイトのビルド後に、別のpreview service用のアダプター専用config/entryを生成します。プレビューはSSRと
assetsを検証しますが、`AI_CHAT_DAILY_SPEND_CAP`をバインドせず、migrationも適用せず、本番の`RATE_LIMIT`
KVとcustom-domain routeもコピーしません。liveのAI admissionとrate-limit経路は本番専用です。

## SKIP_DOC_HISTORY

`SKIP_DOC_HISTORY=1`が設定されると、doc-historyのzfbプラグインはすべてのgit履歴呼び出しをスキップして空のマニフェストを書き出し、すべてのSSGページから作成日・更新日・著者ブロックが消えます。標準CIでは`build-site`ジョブにこのフラグは**設定されません** — preBuildステップが実際の日付を生成するためにフルgitアクセスが必要なためです。

| コンテキスト | 値 | 理由 |
|---|---|---|
| CI `build-site`ジョブ | 未設定 | preBuildがSSGメタブロック（作成日・更新日・著者）を生成するためにフルgit履歴が必要 |
| CI `e2e`ジョブ | 未設定 | E2Eテストはインラインで履歴機能を検証する |
| ローカル`pnpm build` | 未設定 | 作成日・更新日・著者のメタブロックは埋め込まれる（preBuild）。ページごとの履歴ドロップダウンJSONはデフォルトではローカル生成**されない** — 後述のGEN_DOC_HISTORYを参照 |

[Dynamic content unavailable in text export]

`SKIP_DOC_HISTORY=1`はシャロークローンやgit履歴が利用できないカスタムCIバリアント向けのエスケープハッチです。設定するとすべてのページから作成日・更新日・著者ブロックが消えます。



以下のJSON生成の判定表は、`docHistory: true`かつ`docHistoryUi: true`の場合です。`docHistoryUi: false`なら、環境変数にかかわらずプラグインのJSON生成と開発時のプロキシが停止します。preBuildのマニフェストは維持されますが、`SKIP_DOC_HISTORY=1`を指定すると空になります。`DOC_HISTORY_SKIP_POSTBUILD=1`は不要です。ホストの独立した`build-history`ジョブと成果物の受け渡し処理も削除してください。単独の生成CLIはこの設定では停止しません。[ドキュメント履歴ガイド](https://zudo-doc.takazudomodular.com/ja/docs/guides/doc-history)に設定例と環境変数の比較表があります。

## GEN_DOC_HISTORY

`GEN_DOC_HISTORY=1`（#1986で導入）はdoc-historyプラグインの**postBuild**フックを制御します — `dist/doc-history/`にページごとのリビジョン／差分JSONを書き出し、DocHistoryドロップダウンアイランドに供給するステップです。プラグインの**preBuild**フック（表示される作成日・更新日・著者のメタブロック）はこれに影響されず、`SKIP_DOC_HISTORY`のみで制御されます。postBuildは次の決定表に従います：

| コンテキスト | postBuild JSON | 理由 |
|---|---|---|
| `SKIP_DOC_HISTORY=1` | スキップ | 以下すべてを上書きする — `DOC_HISTORY_SKIP_POSTBUILD`、`GEN_DOC_HISTORY`、CIに優先する |
| `DOC_HISTORY_SKIP_POSTBUILD=1` | スキップ | `GEN_DOC_HISTORY`とCIを上書きする — `SKIP_DOC_HISTORY`にのみ劣後する |
| `GEN_DOC_HISTORY=1` | 生成 | 明示的なローカルオプトイン |
| `CI` / `GITHUB_ACTIONS`が設定 | 生成 | `build-site`アーティファクトを以前とバイト単位で同一に保つ |
| 通常のローカル`pnpm build` | スキップ | #1986のデフォルト — 下記のTipを参照 |

[Dynamic content unavailable in text export]

postBuildがローカルでデフォルトオフなのは、コンテンツファイルごとの`git log --follow`チェーンが大規模なコーパスではzfbの120秒のpostBuildライフサイクル予算を超え、通常の`pnpm build`を失敗させるためです。ローカルでは冗長でもあります：開発時は履歴をスタンドアロンの`:4322`サーバーからライブで読み取り、CIは専用の並列`build-history`ジョブでJSONを生成します。ローカルビルドした`dist/`をプレビューするとき（またはTauriオフラインリーダーをビルドするとき）にドロップダウンを動作させるには、`GEN_DOC_HISTORY=1 pnpm build`を実行します。これはzfbプラグインのpostBuildのみを制御する点に注意してください — `build-history`ジョブで使われるスタンドアロンの`@takazudo/zudo-doc-history-server generate` CLIは制御対象外で、呼び出されると常に生成します。



## DOC_HISTORY_SKIP_POSTBUILD

`DOC_HISTORY_SKIP_POSTBUILD=1`（#2927で導入）は、上の決定表にあるpostBuildのページごとのドロップダウンJSONステップのみをスキップします — `GEN_DOC_HISTORY`がオプトインするのと同じステップです。`SKIP_DOC_HISTORY=1`と異なり、preBuildマニフェストは空にしません。そのため、作成日・更新日・著者ブロックには実際のgit日付が引き続き表示されます。

これはシャロークローンのCIバリアント向けです：軽量なpreBuildの`--name-status`走査には十分なgit履歴があるものの、より重いpostBuildのファイルごとの`git log --follow`チェーンは明示的にオプトアウトしたい場合に使います。ローカルビルドのデフォルト（postBuildをスキップ）に頼るのではなく明示的に指定する必要があるのは、そのバリアントが`CI=true`も設定している場合、CIではデフォルトで生成されるためです。

[Dynamic content unavailable in text export]

この名前は意図的に`SKIP_DOC_HISTORY`という部分文字列を含みません。そのため`scripts/check-compatibility-contract.ts`のリテラル文字列によるサバイバースキャンでは別のマーカーとして扱われ、許可リストの変更は不要です。



## ジョブ間のデータ共有

build jobは出力をtarにまとめ、**`actions/upload-artifact`**で公開します。consumer jobは
`actions/download-artifact`を使い、validation/deploy前に`${{ runner.temp }}`から展開します：

```yaml
name: site-dist       # contains site-dist.tar
name: doc-history     # contains doc-history.tar
retention-days: 1
```

**deploy**と**preview** jobは両archiveを復元し、Wranglerの前にdoc historyを`dist/`へmergeします。
**worker-contract** jobも同じ`site-dist` artifactを復元するため、runtime testとdry-runは後で
preview/deployが使うものと同じbuildを検証します。

[Dynamic content unavailable in text export]

tarはartifact upload中も任意のroute filenameを維持します。retentionを1日にすることでstorageを
抑えながらrun単位のpaper trailを残し、このhandoffはmutable cache keyやcross-run cache stateに依存しません。



## 必要なシークレット

リポジトリの**Settings → Secrets and variables → Actions**で設定します：

| シークレット | 説明 |
|---|---|
| `CLOUDFLARE_API_TOKEN` | WorkersデプロイのためのCloudflare APIトークン |
| `CLOUDFLARE_ACCOUNT_ID` | CloudflareアカウントID |
| `IFTTT_PROD_NOTIFY` | デプロイ通知用のIFTTTウェブフックURL（オプション） |

[Dynamic content unavailable in text export]

`CLOUDFLARE_API_TOKEN`には**Workers: Edit**権限が必要です（KVネームスペースを使用する場合は**KV Storage: Edit**も必要）。アカウント全体へのアクセス権限を付与するのではなく、特定のWorkerにスコープすることを推奨します。