Color mode

記事とサイトのリポジトリ分離(詳細編)

このページでは、Riebeckite で Content と Site を分離して運用するときの詳しい仕組みを説明します。

初めて分離構成を作る場合は、先に 記事とサイトのリポジトリ分離 を読んでください。

このページでは、そこから一歩踏み込んで、

  • appRoot / configRoot / contentRoot
  • 外部 Vault の Path 解決
  • 1つの Vault を複数 Site で使う構成
  • CI で別 Repository を取得する方法
  • Private Repository の認証
  • Git Submodule
  • publishStrategy と exclude
  • Attachment の公開
  • 問題が起きたときの調査方法

を扱います。

Diagram source
text
flowchart LR
    Vault["Content Repository<br/>Obsidian Vault"]
    Site["Site Repository<br/>Riebeckite"]
    Build["Build"]
    Output["dist/"]
    Deploy["Deploy"]
 
    Vault --> Build
    Site --> Build
    Build --> Output
    Output --> Deploy

Content の保存場所と、Riebeckite Application の場所は別にできます。

1. 外部 Vault が使える仕組み

Riebeckite では、

ts
content: {
  directory: "../vault",
},

のように、Site の外にある Directory を Content として指定できます。

この Path がどこを基準に解決されるかを理解するには、3つの Root を区別します。

Root 役割 基準
appRoot HonoX / Vite Application Vite の root
configRoot riebeckite.config.ts がある Directory 通常は appRoot
contentRoot 実際に Content を読む Directory path.resolve(appRoot, content.directory)

相対 content.directory は appRoot を基準に解決される点が最も重要です。

Diagram source
text
flowchart TD
    App["appRoot"]
    Config["content.directory<br/>../vault"]
    Resolve["path.resolve()"]
    Content["contentRoot"]
 
    App --> Resolve
    Config --> Resolve
    Resolve --> Content

たとえば、

text
workspace/
├─ vault/
└─ site/          ← appRoot
   ├─ app/
   ├─ public/
   └─ riebeckite.config.ts

なら、

ts
export default defineConfig({
  content: {
    directory: "../vault",
  },
});

と指定できます。

process.cwd() は基準ではない

content.directory を、

ts
process.cwd()

から独自に組み立てないでください。

CLI を実行した場所によって Content Root が変わってしまいます。

text
使わない
  → process.cwd()
 
基準
  → appRoot

また、Vault を Site の外に置くために appRoot 自体を Vault へ変更するのも避けます。

appRoot は Application の Root です。

text
appRoot
├─ app/
├─ public/
├─ route
├─ generated style
└─ build output

Vault は Application ではなく Content Source です。

text
Site
  → appRoot
 
Vault
  → contentRoot

として分離します。

2. Root が決まるまで

CLI では、まず Riebeckite Config を探します。

概念的な流れは次のとおりです。

Diagram source
text
flowchart TD
    CLI["CLI実行"]
    Config["riebeckite.config.*を探す"]
    ConfigRoot["configRoot"]
    Vite["vite.config.*を探す"]
    AppRoot["appRoot"]
    Directory["content.directory"]
    ContentRoot["contentRoot"]
 
    CLI --> Config
    Config --> ConfigRoot
    ConfigRoot --> Vite
    Vite --> AppRoot
    AppRoot --> ContentRoot
    Directory --> ContentRoot

Config を探す

CLI は実行した Working Directory から親へ、

text
riebeckite.config.ts
riebeckite.config.js
riebeckite.config.mjs

を探します。

最初に見つかった Config の Directory が configRoot になります。

見つからなければ、

text
Could not find riebeckite.config.*

で失敗します。

appRoot を決める

次に configRoot の下から、

text
vite.config.ts
vite.config.js
vite.config.mjs

を探します。

node_modules、.git、tests は探索対象外です。

Vite Application が見つからなければ Error になります。

複数見つかった場合も、

text
Found multiple Vite applications

として失敗します。

これは Riebeckite が「どの Application を使うべきか」を一意に判断できないためです。

contentRoot を決める

最後に、

ts
path.resolve(appRoot, content.directory)

によって contentRoot を解決します。

絶対 Path を指定した場合も、最終的には同じ Content Root として扱われます。

Config を Application の外に置く場合

riebeckite.config.ts を Vite Application の外に意図的に置く構成では、riebeckiteVite() に、

text
configRoot
appRoot

を明示できます。

ただし、その場合でも相対 content.directory の基準は appRoot です。

text
configRoot
   ≠
content.directoryの基準
 
content.directory
   ↓
appRootを基準に解決

3. Repository 構成の3パターン

Content と Site の配置は、大きく3つに分けられます。

パターン 構成 向いているケース
A Site と Content が同じ Repository 最も単純な個人 Site
B 同じ Repository 内で Directory を分離 履歴は共有しつつ場所を分けたい
C Content と Site が別 Repository Private Vault、独立した更新

A. 1 Repository

text
blog/
├─ riebeckite.config.ts
├─ app/
├─ public/
└─ content/
   └─ index.md

設定は、

ts
content: {
  directory: "content",
},

です。

create-riebeckite で作成する基本構成です。

B. 同じ Repository 内で分離

text
notes-repo/
├─ site/
│  └─ riebeckite.config.ts
│
└─ vault/

Site 側から、

ts
content: {
  directory: "../vault",
},

と指定します。

Git Repository は同じですが、

text
Application
Content

の Directory を分けられます。

C. 別 Repository

text
Content Repository
  → Private Vault
 
Site Repository
  → Public Riebeckite Site

という構成です。

記事を Private Repository にしたい場合や、Content と Site の更新を独立させたい場合に向いています。

CI では、Site Repository だけでなく Content Repository も取得する必要があります。

4. 1つの Vault を複数 Site で使う

外部 Vault は、複数の Site から利用することもできます。

text
workspace/
├─ notes/       ← 共有Vault
├─ blog/
└─ docs/

それぞれ、

ts
content: {
  directory: "../notes",
},

のように設定できます。

Diagram source
text
flowchart TD
    Vault["Private Vault"]
 
    Vault --> Blog["Blog"]
    Vault --> Docs["Docs"]
 
    Blog --> BlogRules["Blog用<br/>exclude / Plugin"]
    Docs --> DocsRules["Docs用<br/>exclude / Plugin"]

Vault は読み取り専用の Source として扱います。

各 Site はそれぞれ、

  • exclude
  • publishStrategy
  • Plugin
  • Theme
  • Deployment

を独立して設定できます。

つまり、同じ Note を元にしていても、Site ごとに異なる公開範囲や見せ方を設定できます。

5. Private Vault の基本設定

Private Repository の Vault を使う場合は、公開対象を明示する方式が扱いやすくなります。

既定の、

text
publishStrategy: explicit

を利用できます。

公開する Note にだけ、

yaml
publish: true

を指定します。

さらに、明らかに Site で利用しない Directory は exclude します。

ts
content: {
  directory: "../notes",
 
  exclude: [
    ".obsidian/**",
    "Templates/**",
    "private/**",
  ],
},

考え方としては、

text
exclude
  → そもそも読み込ませない
 
publishStrategy
  → 読み込んだContentから公開対象を決める

という違いです。

6. .obsidian/ の扱い

Obsidian Vault には、

text
.obsidian/

があります。

これは Obsidian の設定 Directory なので、Riebeckite の Content として扱わない場合は、

ts
exclude: [
  ".obsidian/**",
],

に追加します。

Git には残しつつ Site から除外することもできます。

Obsidian の Workspace 状態だけ Git に含めたくない場合は、.gitignore で、

text
.obsidian/workspace*.json

を除外する方法もあります。

7. publishStrategy

Riebeckite の公開判定には、

text
explicit
selective

があります。

値 公開条件 考え方
explicit publish: true 公開するものを選ぶ
selective private: true でも draft: true でもない 非公開にするものを選ぶ

Private Vault では、既定の explicit が扱いやすい構成です。

Diagram source
text
flowchart LR
    Vault["Private Vault"]
    Publish{"publish: true?"}
 
    Vault --> Publish
    Publish -->|"Yes"| Public["公開"]
    Publish -->|"No"| Private["公開しない"]

公開判定は Riebeckite の isPublished / isPublishable に集約されています。

Site 側で独自の公開判定を作らず、Riebeckite の公開規則を利用してください。

これによって、

text
Page
Diagnostics
Asset Collection

などで公開判定がずれることを防げます。

8. exclude の Pattern

exclude は contentRoot からの相対 Path に対して適用されます。

Path Separator は / に正規化されます。

注意したいのは、Pattern が Path 全体に Anchor されることです。

Pattern Match Match しない
.obsidian/** .obsidian/app.json notes/.obsidian/app.json
**/.obsidian/** どちらにも Match —
Templates/** Templates/daily.md notes/Templates/daily.md
**/Templates/** どちらにも Match —
private/** private/secret.md notes/private/secret.md

* は1 Segment 内、

text
*

** は複数 Segment をまたいで Match します。

text
**

同名 Directory が Subdirectory にも現れる可能性がある場合は、

text
**/private/**
**/templates/**

のように指定すると確実です。

exclude は Content を読み込む前に適用されます。

そのため、除外された Note は、

  • Link Resolution
  • Content Graph
  • Query
  • Publication

などにも現れません。

9. CI では Content を取得する必要がある

Site と Content が別 Repository の場合、Site Repository の Workflow を開始しただけでは Vault は存在しません。

text
GitHub Actions Runner
 
Site Repository
  → ある
 
Content Repository
  → まだない

そのため、Build 前に Content Repository を Checkout します。

yaml
- name: Check out the site
  uses: actions/checkout@v4
 
- name: Check out the notes
  uses: actions/checkout@v4
  with:
    repository: <you>/notes
    token: ${{ secrets.RIEBECKITE_CONTENT_READ_TOKEN || github.token }}
    path: content

この場合、Runner 上では、

text
site/
├─ app/
├─ riebeckite.config.ts
└─ content/        ← Content Repository

という形になります。

Config も、

ts
content: {
  directory: "content",
},

に合わせます。

10. Checkout と Deploy Trigger は別物

ここは特に重要です。

text
Content RepositoryをCheckoutする
        ≠
Content更新時にBuildを開始する

Checkout は、

Workflow が始まった後に Content を読めるようにする設定

です。

一方、Content Repository への Push から Site を自動 Deploy したい場合は、

Site Workflow を開始する仕組み

も必要です。

Diagram source
text
flowchart LR
    Push["Content Repository<br/>push"]
    Notify["notify-site"]
    Dispatch["repository_dispatch<br/>content-updated"]
    Workflow["Site Workflow"]
    Checkout["Content Checkout"]
    Build["Build"]
    Deploy["Deploy"]
 
    Push --> Notify
    Notify --> Dispatch
    Dispatch --> Workflow
    Workflow --> Checkout
    Checkout --> Build
    Build --> Deploy

つまり、Repository を分離した自動 Deployment には、

text
1. Site Workflowを起動する
2. Content Repositoryを取得する

という2つの仕組みが必要です。

11. Private Repository の認証

Content Repository が Private または Internal の場合は、専用の認証が必要です。

通常の github.token は、現在実行中の Repository を対象とします。

別の Private Repository を読むためには、Site Repository に、

text
RIEBECKITE_CONTENT_READ_TOKEN

を登録します。

この Token は Content Repository を読むためだけに利用します。

Diagram source
text
flowchart LR
    Site["Site Repository"]
    ReadToken["RIEBECKITE_CONTENT_READ_TOKEN<br/>Contents: read"]
    Content["Private Content Repository"]
 
    Site --> ReadToken
    ReadToken --> Content

Fine-grained PAT を使う場合は、対象を Vault Repository に限定し、

text
Contents: read

を与えます。

同等の Read-only GitHub App Installation Token でも構いません。

12. Content から Site を起動する認証

逆方向の、

text
Content Repository
      ↓
Site Repositoryを起動

には別の Token を使います。

Content Repository 側に、

text
SITE_DISPATCH_TOKEN

を登録します。

Diagram source
text
flowchart LR
    Content["Content Repository"]
    Token["SITE_DISPATCH_TOKEN"]
    Site["Site Repository"]
 
    Content --> Token
    Token -->|"repository_dispatch"| Site

Fine-grained PAT を利用する場合は、Site Repository だけを対象に必要な権限を与えます。

元の構成では、

text
Contents: read and write

を利用します。

Classic PAT なら repo Scope、GitHub App Token なら Contents: write が必要です。

PAT は GitHub の Settings → Developer settings → Personal access tokens から作成し、作成した値をそれぞれの Repository の Settings → Secrets and variables → Actions に登録します。

2つの Token は役割を分けて使います。

text
RIEBECKITE_CONTENT_READ_TOKEN
  → SiteからPrivate Contentを読む
 
SITE_DISPATCH_TOKEN
  → ContentからSiteのWorkflowを起動する

13. Content の Version を固定するか

追加 Checkout で特定の ref を指定しなければ、Content Repository の Default Branch の最新状態を取得できます。

記事更新をそのまま Site に反映する運用なら、この方法が扱いやすくなります。

text
Content main
   ↓
push
   ↓
Site Workflow
   ↓
その時点の最新Content

既定の fetch-depth: 1 で十分です。

一方、Site が使用する Content の Commit を明示的に固定したい場合は Git Submodule という選択肢があります。

14. Git Submodule を使う

Site Repository から Content Repository を Submodule として登録できます。

sh
cd my-site
git submodule add git@github.com:<you>/notes.git content

Config は、

ts
content: {
  directory: "content",
},

とします。

CI では、

yaml
uses: actions/checkout@v4
with:
  submodules: recursive

のように Submodule も取得します。

Submodule の注意点

Submodule は Content Repository の 特定 Commit を Site Repository に記録します。

そのため、

text
Content Repositoryを更新
        ↓
SiteのSubmodule参照
        ↓
自動では変わらない

という特徴があります。

新しい Content を利用するには Site 側でも、

sh
cd content
git pull
 
cd ..
git add content
git commit -m "記事を更新"

のように参照を更新します。

git submodule update --remote を利用することもできますが、最終的には Site Repository 側で新しい Submodule Commit を記録する必要があります。

追加 Checkout と Submodule

観点 追加 Checkout Submodule
Content Push から自動 Deploy Dispatch を設定すれば可能 Site 側の参照更新が必要
Content の取得先 Workflow で決める content など
Version Branch の最新へ追従しやすい Commit 単位で固定
手元の操作 通常の Clone で済みやすい Submodule 操作が必要
向いているケース Content 更新が中心 Content Version を Site 側で固定したい

記事を頻繁に更新する Site なら、追加 Checkout と Repository Dispatch の構成が扱いやすくなります。

Content の Version を Site Repository から厳密に固定したい場合は Submodule が利用できます。

15. 手元と CI の Directory

相対 content.directory は appRoot 基準です。

そのため、手元と CI で Directory 構成が違えば、設定する Path も変わります。

環境 配置 directory
手元 workspace/notes と workspace/my-site "../notes"
CI my-site/notes "notes"
Submodule my-site/content "content"

たとえば手元では、

text
workspace/
├─ notes/
└─ my-site/

なので、

ts
directory: "../notes"

となります。

一方 CI で、

text
my-site/
├─ app/
├─ notes/
└─ riebeckite.config.ts

と Checkout したなら、

ts
directory: "notes"

です。

どちらも appRoot から Content Root への Path です。

可能なら、Local と CI の Layout を揃えておくと設定を単純にできます。

16. Content image と Attachment は公開方法が異なる

Vault 内のファイルは3種類に分かれ、公開を担当する場所も異なります。

種類 対象 公開 URL 公開の担当
Content image 画像(png、jpg、svg など) /<Vault からの相対 logical path> build 時に generated output として書き出される
Attachment / Media Markdown でも画像でもないファイル /assets/attachments/<Vault からの相対 logical path> Site 側の Prebuild
Static asset Site 自身が管理するファイル / 配下 Vite の public/

Content image に Site 側の作業は不要です。obsidianMarkdown() が公開ページから参照されている image を logical path を保ったまま build の出力に書き出します。開発サーバーでも同じ logical path のまま Content から配信されます。参照されていない image、非公開ページからの image は書き出されません。

一方の attachment / media は、

md
![[attachments/report.pdf]]

のような Embed が URL を生成しても、

Vault にある File 自体が自動的に Public Directory へコピーされるわけではありません。

Diagram source
text
flowchart LR
    Image["content image<br/>assets/logo.png"]
    Attach["![[attachments/report.pdf]]"]
 
    Image -->|"build が書き出す"| Public["Public Asset"]
    Attach -->|"URL だけ生成"| Copy["Prebuild Copy"]
    Copy --> Public

公開する attachment / media は Site 側の Prebuild 処理でコピーします。

Riebeckite Repository では、

text
apps/web/scripts/build_images.ts

が参照実装です。

prebuild から、

text
tsx scripts/build_images.ts

として実行します。

17. 公開する Asset だけをコピーする

Vault 全体を public/ へコピーするのは避けてください。

参照実装では、

  1. contentRoot 内の画像・Attachment を調べる
  2. ContentManager で公開 Content を Build する
  3. 公開 Note から参照されている Asset を集める
  4. 必要な Asset だけ Public Directory へコピーする
  5. Public 側に残った不要な Attachment を削除する

という流れになります。

Content image は build が公開対象を判断して書き出すため、この Prebuild 処理は主に attachment / media を担当します。参照実装は画像も public/ へ写しますが、これは初回 build 前の開発サーバーでも配信できるようにするためです。

Diagram source
text
flowchart TD
    Vault["Vault Assets"]
    PublicNotes["公開Note"]
    Referenced["参照されているAsset"]
    Copy["Copy"]
    Public["Public Assets"]
 
    Vault --> Referenced
    PublicNotes --> Referenced
    Referenced --> Copy
    Copy --> Public

つまり、

text
Vaultに存在する
  → コピー

ではなく、

text
公開Noteから参照されている
  → コピー

です。

Vault 全体をコピーすると、

  • 非公開 Note からの image
  • 非公開 Note の Attachment
  • 未使用 Attachment
  • .obsidian Metadata

などを誤って公開する可能性があります。

18. Asset の Public URL

Content image は Vault からの Logical Path をそのまま保ちます。

text
Vault:
assets/logo.png
 
Public URL:
/assets/logo.png

Attachment / Media には専用の Prefix が使われるため、同じ論理 path に画像があっても衝突しません。

text
/assets/attachments/<Vaultからの相対logical path>

たとえば、

text
Vault:
attachments/report.pdf
 
Public URL:
/assets/attachments/attachments/report.pdf

のように、Vault Root からの Logical Path を基準にします。

attachment() は解決済み Vault Root から File Size を読み、Root 外への Path を拒否します。

media() も同じ Logical Path を使って Audio / Video を描画します。

Public Directory 上の物理配置と URL の対応を揃えておくことで、Build 後の 404 を避けやすくなります。

19. 公開境界を考える

Repository を Private にすることと、Riebeckite で何を公開するかは別の問題です。

text
Repository Visibility
  → Git Repositoryを誰が読めるか
 
Riebeckite Publication
  → Siteに何を出すか

Private Vault を利用していても、Build 時に誤って非公開情報を Public Output へコピーすれば公開されてしまいます。

そのため、

text
exclude
      +
publishStrategy
      +
公開Noteだけを対象にしたAsset Copy

の3つを揃えて考えます。

特に Asset は Vault 全体をそのまま public/ へコピーしないようにしてください。

公開ノートから未公開ノートへの参照は、@riebeckite/plugin-diagnostics の riebeckite-diagnostics で publish-boundary 警告として確認できます。runDiagnostics() を使うと、同じ診断をプログラムからも取得できます。詳細は Diagnostics を参照してください。

20. 検証する

Root、Content、公開境界を確認するときは、次の順番で調べます。

sh
npm exec riebeckite check
npm exec riebeckite doctor
npm exec riebeckite inspect config
npm exec -- riebeckite inspect content --list
npm exec riebeckite inspect graph
npm exec riebeckite build

check

sh
npm exec riebeckite check

Config と Plugin Contract を検証します。

doctor

sh
npm exec riebeckite doctor

読み込めない Content Source や、不正な Filesystem Content Source などを確認します。

inspect config

sh
npm exec riebeckite inspect config

まずここで、

text
Directory
Publishing
Exclude

を確認します。

Directory は解決済みの絶対 Path です。

ここで Riebeckite が本当に目的の Vault を見ているか確認します。

inspect content --list

sh
npm exec -- riebeckite inspect content --list

期待している Logical Path が Content として読み込まれているか確認します。

WikiLink や Embed を調査する前に、まず Content 自体が存在するか確認してください。

inspect graph

sh
npm exec riebeckite inspect graph

除外したはずの Note が Graph に残っていないか確認できます。

build

最後に、

sh
npm exec riebeckite build

で Integration と Route Rendering を含む実際の Build を確認します。

Diagram source
text
flowchart LR
    Check["check"]
    Doctor["doctor"]
    Config["inspect config"]
    Content["inspect content"]
    Graph["inspect graph"]
    Build["build"]
 
    Check --> Doctor
    Doctor --> Config
    Config --> Content
    Content --> Graph
    Graph --> Build

21. Working Directory に依存していないか確認する

Root Resolution の問題を調べる場合は、Site Root だけでなく Nested Directory から CLI を実行してみる方法もあります。

たとえば、

sh
cd site/app
 
npm exec riebeckite inspect config
npm exec -- riebeckite inspect content --list

としても同じ Application / Vault が解決されることを確認します。

ただし、無関係な Directory から実行した場合は Config 自体を発見できないことがあります。

トラブルシューティング

症状 確認すること
記事が表示されない publish: true、content.exclude、inspect content --list
doctor が Content Source を報告する inspect config で解決済み Directory を確認
Could not find riebeckite.config.* CLI を Site の外から実行していないか
Found multiple Vite applications vite.config.* が複数ないか
CI で Vault が見つからない Content の追加 Checkout または Submodule
Private Vault を Checkout できない Read Token と権限
Submodule が CI にない submodules: recursive
Submodule の記事が古い Site 側の Submodule Commit を更新
Deploy 後に画像が 404 公開 Note からの参照と build の出力
Deploy 後に Attachment が 404 Prebuild Copy と Public Asset Path
Vault にある画像がコピーされない 参照元 Note が公開対象か
Local では動くが CI では Path が違う appRoot と Checkout 先
exclude が効かない Pattern の Anchor と **/

よくある Path の問題

Content が見つからない場合は、まず、

text
「今どこからCommandを実行しているか」

ではなく、

text
「RiebeckiteがどのappRootを解決したか」

を確認します。

そのために、

sh
npm exec riebeckite inspect config

を利用します。

相対 content.directory の基準は appRoot です。

text
process.cwd()
  ×
 
configRoot
  ×
 
appRoot
  ○

よくある CI の問題

CI の問題は、

text
Workflowが起動しない

のか、

text
Workflowは起動するがContentがない

のかを最初に分けます。

Diagram source
text
flowchart TD
    Problem["記事をPushしてもDeployされない"]
 
    Problem --> Running{"Site Workflowは<br/>起動した?"}
 
    Running -->|"No"| Dispatch["repository_dispatch /<br/>SITE_DISPATCH_TOKEN"]
    Running -->|"Yes"| Content{"Contentは<br/>Checkoutできた?"}
 
    Content -->|"No"| Token["RIEBECKITE_CONTENT_READ_TOKEN /<br/>checkout設定"]
    Content -->|"Yes"| Build["Build Logを確認"]

この2つは別の仕組みなので、問題を切り分けて確認してください。

まとめ

Content と Site を分離するときは、4つの境界を分けて考えると整理しやすくなります。

Diagram source
text
flowchart TD
    Storage["1. Repository<br/>どこに保存する?"]
    Source["2. Content Source<br/>どこから読む?"]
    Trigger["3. Deployment Trigger<br/>いつBuildする?"]
    Publication["4. Publication<br/>何を公開する?"]
 
    Storage --> Source
    Source --> Trigger
    Trigger --> Publication

それぞれ、

text
Repository
  → ContentとSiteをどこに保存するか
 
content.directory
  → RiebeckiteがどこからContentを読むか
 
Checkout / repository_dispatch
  → CIでどう取得し、いつBuildするか
 
publishStrategy / exclude / Asset Copy
  → 何をPublic Siteへ出すか

を担当します。

特に、

text
Content RepositoryをPrivateにする
        ≠
自動的に公開境界が安全になる

という関係に注意してください。

Private Vault を利用する場合でも、publishStrategy、exclude、Asset Copy のすべてで Public Boundary を維持してください。

関連資料

History

1 changesCollapseExpand
1 + # 記事とサイトのリポジトリ分離(詳細編)
2 +
3 + このページでは、Riebeckite で **Content と Site を分離して運用するときの詳しい仕組み**を説明します。
4 +
5 + 初めて分離構成を作る場合は、先に [記事とサイトのリポジトリ分離](../content-repositories.ja.md) を読んでください。
6 +
7 + このページでは、そこから一歩踏み込んで、
8 +
9 + - `appRoot` / `configRoot` / `contentRoot`
10 + - 外部 Vault の Path 解決
11 + - 1つの Vault を複数 Site で使う構成
12 + - CI で別 Repository を取得する方法
13 + - Private Repository の認証
14 + - Git Submodule
15 + - `publishStrategy` と `exclude`
16 + - Attachment の公開
17 + - 問題が起きたときの調査方法
18 +
19 + を扱います。
20 +
21 + ```mermaid id="bc2fw5"
22 + flowchart LR
23 + Vault["Content Repository<br/>Obsidian Vault"]
24 + Site["Site Repository<br/>Riebeckite"]
25 + Build["Build"]
26 + Output["dist/"]
27 + Deploy["Deploy"]
28 +
29 + Vault --> Build
30 + Site --> Build
31 + Build --> Output
32 + Output --> Deploy
33 + ```
34 +
35 + **Content の保存場所と、Riebeckite Application の場所は別にできます**。
36 +
37 + ## 1. 外部 Vault が使える仕組み
38 +
39 + Riebeckite では、
40 +
41 + ```ts id="2k5h0f"
42 + content: {
43 + directory: "../vault",
44 + },
45 + ```
46 +
47 + のように、Site の外にある Directory を Content として指定できます。
48 +
49 + この Path がどこを基準に解決されるかを理解するには、3つの Root を区別します。
50 +
51 + | Root | 役割 | 基準 |
52 + | --- | --- | --- |
53 + | `appRoot` | HonoX / Vite Application | Vite の `root` |
54 + | `configRoot` | `riebeckite.config.ts` がある Directory | 通常は `appRoot` |
55 + | `contentRoot` | 実際に Content を読む Directory | `path.resolve(appRoot, content.directory)` |
56 +
57 + **相対 `content.directory` は `appRoot` を基準に解決される**点が最も重要です。
58 +
59 + ```mermaid id="qeg4i1"
60 + flowchart TD
61 + App["appRoot"]
62 + Config["content.directory<br/>../vault"]
63 + Resolve["path.resolve()"]
64 + Content["contentRoot"]
65 +
66 + App --> Resolve
67 + Config --> Resolve
68 + Resolve --> Content
69 + ```
70 +
71 + たとえば、
72 +
73 + ```text id="zmvy0v"
74 + workspace/
75 + ├─ vault/
76 + └─ site/ ← appRoot
77 + ├─ app/
78 + ├─ public/
79 + └─ riebeckite.config.ts
80 + ```
81 +
82 + なら、
83 +
84 + ```ts id="0btcdw"
85 + export default defineConfig({
86 + content: {
87 + directory: "../vault",
88 + },
89 + });
90 + ```
91 +
92 + と指定できます。
93 +
94 + ## `process.cwd()` は基準ではない
95 +
96 + `content.directory` を、
97 +
98 + ```ts id="5r4v23"
99 + process.cwd()
100 + ```
101 +
102 + から独自に組み立てないでください。
103 +
104 + CLI を実行した場所によって Content Root が変わってしまいます。
105 +
106 + ```text id="62ipd3"
107 + 使わない
108 + → process.cwd()
109 +
110 + 基準
111 + → appRoot
112 + ```
113 +
114 + また、Vault を Site の外に置くために `appRoot` 自体を Vault へ変更するのも避けます。
115 +
116 + `appRoot` は Application の Root です。
117 +
118 + ```text id="z24s1v"
119 + appRoot
120 + ├─ app/
121 + ├─ public/
122 + ├─ route
123 + ├─ generated style
124 + └─ build output
125 + ```
126 +
127 + Vault は Application ではなく **Content Source** です。
128 +
129 + ```text id="u5fzdb"
130 + Site
131 + → appRoot
132 +
133 + Vault
134 + → contentRoot
135 + ```
136 +
137 + として分離します。
138 +
139 + ## 2. Root が決まるまで
140 +
141 + CLI では、まず Riebeckite Config を探します。
142 +
143 + 概念的な流れは次のとおりです。
144 +
145 + ```mermaid id="r6y9gr"
146 + flowchart TD
147 + CLI["CLI実行"]
148 + Config["riebeckite.config.*を探す"]
149 + ConfigRoot["configRoot"]
150 + Vite["vite.config.*を探す"]
151 + AppRoot["appRoot"]
152 + Directory["content.directory"]
153 + ContentRoot["contentRoot"]
154 +
155 + CLI --> Config
156 + Config --> ConfigRoot
157 + ConfigRoot --> Vite
158 + Vite --> AppRoot
159 + AppRoot --> ContentRoot
160 + Directory --> ContentRoot
161 + ```
162 +
163 + ### Config を探す
164 +
165 + CLI は実行した Working Directory から親へ、
166 +
167 + ```text id="wjavmb"
168 + riebeckite.config.ts
169 + riebeckite.config.js
170 + riebeckite.config.mjs
171 + ```
172 +
173 + を探します。
174 +
175 + 最初に見つかった Config の Directory が `configRoot` になります。
176 +
177 + 見つからなければ、
178 +
179 + ```text id="sjj16r"
180 + Could not find riebeckite.config.*
181 + ```
182 +
183 + で失敗します。
184 +
185 + ### `appRoot` を決める
186 +
187 + 次に `configRoot` の下から、
188 +
189 + ```text id="q74qlm"
190 + vite.config.ts
191 + vite.config.js
192 + vite.config.mjs
193 + ```
194 +
195 + を探します。
196 +
197 + `node_modules`、`.git`、`tests` は探索対象外です。
198 +
199 + Vite Application が見つからなければ Error になります。
200 +
201 + 複数見つかった場合も、
202 +
203 + ```text id="y62ikq"
204 + Found multiple Vite applications
205 + ```
206 +
207 + として失敗します。
208 +
209 + これは Riebeckite が「どの Application を使うべきか」を一意に判断できないためです。
210 +
211 + ### `contentRoot` を決める
212 +
213 + 最後に、
214 +
215 + ```ts id="9h2i4n"
216 + path.resolve(appRoot, content.directory)
217 + ```
218 +
219 + によって `contentRoot` を解決します。
220 +
221 + 絶対 Path を指定した場合も、最終的には同じ Content Root として扱われます。
222 +
223 + ## Config を Application の外に置く場合
224 +
225 + `riebeckite.config.ts` を Vite Application の外に意図的に置く構成では、`riebeckiteVite()` に、
226 +
227 + ```text id="c8kv96"
228 + configRoot
229 + appRoot
230 + ```
231 +
232 + を明示できます。
233 +
234 + ただし、その場合でも相対 `content.directory` の基準は `appRoot` です。
235 +
236 + ```text id="lpt0pf"
237 + configRoot
238 + ≠
239 + content.directoryの基準
240 +
241 + content.directory
242 + ↓
243 + appRootを基準に解決
244 + ```
245 +
246 + ## 3. Repository 構成の3パターン
247 +
248 + Content と Site の配置は、大きく3つに分けられます。
249 +
250 + | パターン | 構成 | 向いているケース |
251 + | --- | --- | --- |
252 + | A | Site と Content が同じ Repository | 最も単純な個人 Site |
253 + | B | 同じ Repository 内で Directory を分離 | 履歴は共有しつつ場所を分けたい |
254 + | C | Content と Site が別 Repository | Private Vault、独立した更新 |
255 +
256 + ### A. 1 Repository
257 +
258 + ```text id="1z6dgc"
259 + blog/
260 + ├─ riebeckite.config.ts
261 + ├─ app/
262 + ├─ public/
263 + └─ content/
264 + └─ index.md
265 + ```
266 +
267 + 設定は、
268 +
269 + ```ts id="fbb2ag"
270 + content: {
271 + directory: "content",
272 + },
273 + ```
274 +
275 + です。
276 +
277 + `create-riebeckite` で作成する基本構成です。
278 +
279 + ### B. 同じ Repository 内で分離
280 +
281 + ```text id="0xlm9v"
282 + notes-repo/
283 + ├─ site/
284 + │ └─ riebeckite.config.ts
285 + │
286 + └─ vault/
287 + ```
288 +
289 + Site 側から、
290 +
291 + ```ts id="ejcc13"
292 + content: {
293 + directory: "../vault",
294 + },
295 + ```
296 +
297 + と指定します。
298 +
299 + Git Repository は同じですが、
300 +
301 + ```text id="6d3m50"
302 + Application
303 + Content
304 + ```
305 +
306 + の Directory を分けられます。
307 +
308 + ### C. 別 Repository
309 +
310 + ```text id="r2wghf"
311 + Content Repository
312 + → Private Vault
313 +
314 + Site Repository
315 + → Public Riebeckite Site
316 + ```
317 +
318 + という構成です。
319 +
320 + 記事を Private Repository にしたい場合や、Content と Site の更新を独立させたい場合に向いています。
321 +
322 + CI では、Site Repository だけでなく Content Repository も取得する必要があります。
323 +
324 + ## 4. 1つの Vault を複数 Site で使う
325 +
326 + 外部 Vault は、複数の Site から利用することもできます。
327 +
328 + ```text id="uwhz12"
329 + workspace/
330 + ├─ notes/ ← 共有Vault
331 + ├─ blog/
332 + └─ docs/
333 + ```
334 +
335 + それぞれ、
336 +
337 + ```ts id="a6p3yu"
338 + content: {
339 + directory: "../notes",
340 + },
341 + ```
342 +
343 + のように設定できます。
344 +
345 + ```mermaid id="mypsn6"
346 + flowchart TD
347 + Vault["Private Vault"]
348 +
349 + Vault --> Blog["Blog"]
350 + Vault --> Docs["Docs"]
351 +
352 + Blog --> BlogRules["Blog用<br/>exclude / Plugin"]
353 + Docs --> DocsRules["Docs用<br/>exclude / Plugin"]
354 + ```
355 +
356 + Vault は読み取り専用の Source として扱います。
357 +
358 + 各 Site はそれぞれ、
359 +
360 + - `exclude`
361 + - `publishStrategy`
362 + - Plugin
363 + - Theme
364 + - Deployment
365 +
366 + を独立して設定できます。
367 +
368 + つまり、同じ Note を元にしていても、Site ごとに異なる公開範囲や見せ方を設定できます。
369 +
370 + ## 5. Private Vault の基本設定
371 +
372 + Private Repository の Vault を使う場合は、**公開対象を明示する方式**が扱いやすくなります。
373 +
374 + 既定の、
375 +
376 + ```text id="14wd1h"
377 + publishStrategy: explicit
378 + ```
379 +
380 + を利用できます。
381 +
382 + 公開する Note にだけ、
383 +
384 + ```yaml id="81bl75"
385 + publish: true
386 + ```
387 +
388 + を指定します。
389 +
390 + さらに、明らかに Site で利用しない Directory は `exclude` します。
391 +
392 + ```ts id="l29bqk"
393 + content: {
394 + directory: "../notes",
395 +
396 + exclude: [
397 + ".obsidian/**",
398 + "Templates/**",
399 + "private/**",
400 + ],
401 + },
402 + ```
403 +
404 + 考え方としては、
405 +
406 + ```text id="2psu77"
407 + exclude
408 + → そもそも読み込ませない
409 +
410 + publishStrategy
411 + → 読み込んだContentから公開対象を決める
412 + ```
413 +
414 + という違いです。
415 +
416 + ## 6. `.obsidian/` の扱い
417 +
418 + Obsidian Vault には、
419 +
420 + ```text id="4v27t6"
421 + .obsidian/
422 + ```
423 +
424 + があります。
425 +
426 + これは Obsidian の設定 Directory なので、Riebeckite の Content として扱わない場合は、
427 +
428 + ```ts id="jxuwz3"
429 + exclude: [
430 + ".obsidian/**",
431 + ],
432 + ```
433 +
434 + に追加します。
435 +
436 + Git には残しつつ Site から除外することもできます。
437 +
438 + Obsidian の Workspace 状態だけ Git に含めたくない場合は、`.gitignore` で、
439 +
440 + ```text id="cv48fa"
441 + .obsidian/workspace*.json
442 + ```
443 +
444 + を除外する方法もあります。
445 +
446 + ## 7. `publishStrategy`
447 +
448 + Riebeckite の公開判定には、
449 +
450 + ```text id="vgk6x6"
451 + explicit
452 + selective
453 + ```
454 +
455 + があります。
456 +
457 + | 値 | 公開条件 | 考え方 |
458 + | --- | --- | --- |
459 + | `explicit` | `publish: true` | 公開するものを選ぶ |
460 + | `selective` | `private: true` でも `draft: true` でもない | 非公開にするものを選ぶ |
461 +
462 + Private Vault では、既定の `explicit` が扱いやすい構成です。
463 +
464 + ```mermaid id="yov5gt"
465 + flowchart LR
466 + Vault["Private Vault"]
467 + Publish{"publish: true?"}
468 +
469 + Vault --> Publish
470 + Publish -->|"Yes"| Public["公開"]
471 + Publish -->|"No"| Private["公開しない"]
472 + ```
473 +
474 + 公開判定は Riebeckite の `isPublished` / `isPublishable` に集約されています。
475 +
476 + Site 側で独自の公開判定を作らず、Riebeckite の公開規則を利用してください。
477 +
478 + これによって、
479 +
480 + ```text id="e3z54c"
481 + Page
482 + Diagnostics
483 + Asset Collection
484 + ```
485 +
486 + などで公開判定がずれることを防げます。
487 +
488 + ## 8. `exclude` の Pattern
489 +
490 + `exclude` は `contentRoot` からの相対 Path に対して適用されます。
491 +
492 + Path Separator は `/` に正規化されます。
493 +
494 + 注意したいのは、Pattern が **Path 全体に Anchor される**ことです。
495 +
496 + | Pattern | Match | Match しない |
497 + | --- | --- | --- |
498 + | `.obsidian/**` | `.obsidian/app.json` | `notes/.obsidian/app.json` |
499 + | `**/.obsidian/**` | どちらにも Match | — |
500 + | `Templates/**` | `Templates/daily.md` | `notes/Templates/daily.md` |
501 + | `**/Templates/**` | どちらにも Match | — |
502 + | `private/**` | `private/secret.md` | `notes/private/secret.md` |
503 +
504 + `*` は1 Segment 内、
505 +
506 + ```text id="n6yd53"
507 + *
508 + ```
509 +
510 + `**` は複数 Segment をまたいで Match します。
511 +
512 + ```text id="r38f8w"
513 + **
514 + ```
515 +
516 + 同名 Directory が Subdirectory にも現れる可能性がある場合は、
517 +
518 + ```text id="4k5csf"
519 + **/private/**
520 + **/templates/**
521 + ```
522 +
523 + のように指定すると確実です。
524 +
525 + `exclude` は Content を読み込む前に適用されます。
526 +
527 + そのため、除外された Note は、
528 +
529 + - Link Resolution
530 + - Content Graph
531 + - Query
532 + - Publication
533 +
534 + などにも現れません。
535 +
536 + ## 9. CI では Content を取得する必要がある
537 +
538 + Site と Content が別 Repository の場合、Site Repository の Workflow を開始しただけでは Vault は存在しません。
539 +
540 + ```text id="s7pl0c"
541 + GitHub Actions Runner
542 +
543 + Site Repository
544 + → ある
545 +
546 + Content Repository
547 + → まだない
548 + ```
549 +
550 + そのため、Build 前に Content Repository を Checkout します。
551 +
552 + ```yaml id="c66xzk"
553 + - name: Check out the site
554 + uses: actions/checkout@v4
555 +
556 + - name: Check out the notes
557 + uses: actions/checkout@v4
558 + with:
559 + repository: <you>/notes
560 + token: ${{ secrets.RIEBECKITE_CONTENT_READ_TOKEN || github.token }}
561 + path: content
562 + ```
563 +
564 + この場合、Runner 上では、
565 +
566 + ```text id="v2r5x6"
567 + site/
568 + ├─ app/
569 + ├─ riebeckite.config.ts
570 + └─ content/ ← Content Repository
571 + ```
572 +
573 + という形になります。
574 +
575 + Config も、
576 +
577 + ```ts id="m20ehm"
578 + content: {
579 + directory: "content",
580 + },
581 + ```
582 +
583 + に合わせます。
584 +
585 + ## 10. Checkout と Deploy Trigger は別物
586 +
587 + ここは特に重要です。
588 +
589 + ```text id="x7b6a3"
590 + Content RepositoryをCheckoutする
591 + ≠
592 + Content更新時にBuildを開始する
593 + ```
594 +
595 + Checkout は、
596 +
597 + **Workflow が始まった後に Content を読めるようにする設定**
598 +
599 + です。
600 +
601 + 一方、Content Repository への Push から Site を自動 Deploy したい場合は、
602 +
603 + **Site Workflow を開始する仕組み**
604 +
605 + も必要です。
606 +
607 + ```mermaid id="at7b5m"
608 + flowchart LR
609 + Push["Content Repository<br/>push"]
610 + Notify["notify-site"]
611 + Dispatch["repository_dispatch<br/>content-updated"]
612 + Workflow["Site Workflow"]
613 + Checkout["Content Checkout"]
614 + Build["Build"]
615 + Deploy["Deploy"]
616 +
617 + Push --> Notify
618 + Notify --> Dispatch
619 + Dispatch --> Workflow
620 + Workflow --> Checkout
621 + Checkout --> Build
622 + Build --> Deploy
623 + ```
624 +
625 + つまり、Repository を分離した自動 Deployment には、
626 +
627 + ```text id="dgwqrv"
628 + 1. Site Workflowを起動する
629 + 2. Content Repositoryを取得する
630 + ```
631 +
632 + という2つの仕組みが必要です。
633 +
634 + ## 11. Private Repository の認証
635 +
636 + Content Repository が Private または Internal の場合は、専用の認証が必要です。
637 +
638 + 通常の `github.token` は、現在実行中の Repository を対象とします。
639 +
640 + 別の Private Repository を読むためには、Site Repository に、
641 +
642 + ```text id="i8c2y6"
643 + RIEBECKITE_CONTENT_READ_TOKEN
644 + ```
645 +
646 + を登録します。
647 +
648 + この Token は Content Repository を読むためだけに利用します。
649 +
650 + ```mermaid id="tmf07q"
651 + flowchart LR
652 + Site["Site Repository"]
653 + ReadToken["RIEBECKITE_CONTENT_READ_TOKEN<br/>Contents: read"]
654 + Content["Private Content Repository"]
655 +
656 + Site --> ReadToken
657 + ReadToken --> Content
658 + ```
659 +
660 + Fine-grained PAT を使う場合は、対象を Vault Repository に限定し、
661 +
662 + ```text id="6ulddm"
663 + Contents: read
664 + ```
665 +
666 + を与えます。
667 +
668 + 同等の Read-only GitHub App Installation Token でも構いません。
669 +
670 + ## 12. Content から Site を起動する認証
671 +
672 + 逆方向の、
673 +
674 + ```text id="12ozs6"
675 + Content Repository
676 + ↓
677 + Site Repositoryを起動
678 + ```
679 +
680 + には別の Token を使います。
681 +
682 + Content Repository 側に、
683 +
684 + ```text id="wq63pe"
685 + SITE_DISPATCH_TOKEN
686 + ```
687 +
688 + を登録します。
689 +
690 + ```mermaid id="fw61j6"
691 + flowchart LR
692 + Content["Content Repository"]
693 + Token["SITE_DISPATCH_TOKEN"]
694 + Site["Site Repository"]
695 +
696 + Content --> Token
697 + Token -->|"repository_dispatch"| Site
698 + ```
699 +
700 + Fine-grained PAT を利用する場合は、Site Repository だけを対象に必要な権限を与えます。
701 +
702 + 元の構成では、
703 +
704 + ```text id="zyx6fq"
705 + Contents: read and write
706 + ```
707 +
708 + を利用します。
709 +
710 + Classic PAT なら `repo` Scope、GitHub App Token なら `Contents: write` が必要です。
711 +
712 + PAT は [GitHub の Settings → Developer settings → Personal access tokens](https://github.com/settings/tokens) から作成し、作成した値をそれぞれの Repository の **Settings → Secrets and variables → Actions** に登録します。
713 +
714 + 2つの Token は役割を分けて使います。
715 +
716 + ```text id="zhft93"
717 + RIEBECKITE_CONTENT_READ_TOKEN
718 + → SiteからPrivate Contentを読む
719 +
720 + SITE_DISPATCH_TOKEN
721 + → ContentからSiteのWorkflowを起動する
722 + ```
723 +
724 + ## 13. Content の Version を固定するか
725 +
726 + 追加 Checkout で特定の `ref` を指定しなければ、Content Repository の Default Branch の最新状態を取得できます。
727 +
728 + 記事更新をそのまま Site に反映する運用なら、この方法が扱いやすくなります。
729 +
730 + ```text id="v84yd1"
731 + Content main
732 + ↓
733 + push
734 + ↓
735 + Site Workflow
736 + ↓
737 + その時点の最新Content
738 + ```
739 +
740 + 既定の `fetch-depth: 1` で十分です。
741 +
742 + 一方、Site が使用する Content の Commit を明示的に固定したい場合は Git Submodule という選択肢があります。
743 +
744 + ## 14. Git Submodule を使う
745 +
746 + Site Repository から Content Repository を Submodule として登録できます。
747 +
748 + ```sh id="d1a6sp"
749 + cd my-site
750 + git submodule add git@github.com:<you>/notes.git content
751 + ```
752 +
753 + Config は、
754 +
755 + ```ts id="3b21ny"
756 + content: {
757 + directory: "content",
758 + },
759 + ```
760 +
761 + とします。
762 +
763 + CI では、
764 +
765 + ```yaml id="t7w03x"
766 + uses: actions/checkout@v4
767 + with:
768 + submodules: recursive
769 + ```
770 +
771 + のように Submodule も取得します。
772 +
773 + ## Submodule の注意点
774 +
775 + Submodule は Content Repository の **特定 Commit** を Site Repository に記録します。
776 +
777 + そのため、
778 +
779 + ```text id="6dfjui"
780 + Content Repositoryを更新
781 + ↓
782 + SiteのSubmodule参照
783 + ↓
784 + 自動では変わらない
785 + ```
786 +
787 + という特徴があります。
788 +
789 + 新しい Content を利用するには Site 側でも、
790 +
791 + ```sh id="f9tyn1"
792 + cd content
793 + git pull
794 +
795 + cd ..
796 + git add content
797 + git commit -m "記事を更新"
798 + ```
799 +
800 + のように参照を更新します。
801 +
802 + `git submodule update --remote` を利用することもできますが、最終的には Site Repository 側で新しい Submodule Commit を記録する必要があります。
803 +
804 + ## 追加 Checkout と Submodule
805 +
806 + | 観点 | 追加 Checkout | Submodule |
807 + | --- | --- | --- |
808 + | Content Push から自動 Deploy | Dispatch を設定すれば可能 | Site 側の参照更新が必要 |
809 + | Content の取得先 | Workflow で決める | `content` など |
810 + | Version | Branch の最新へ追従しやすい | Commit 単位で固定 |
811 + | 手元の操作 | 通常の Clone で済みやすい | Submodule 操作が必要 |
812 + | 向いているケース | Content 更新が中心 | Content Version を Site 側で固定したい |
813 +
814 + 記事を頻繁に更新する Site なら、追加 Checkout と Repository Dispatch の構成が扱いやすくなります。
815 +
816 + Content の Version を Site Repository から厳密に固定したい場合は Submodule が利用できます。
817 +
818 + ## 15. 手元と CI の Directory
819 +
820 + 相対 `content.directory` は `appRoot` 基準です。
821 +
822 + そのため、手元と CI で Directory 構成が違えば、設定する Path も変わります。
823 +
824 + | 環境 | 配置 | `directory` |
825 + | --- | --- | --- |
826 + | 手元 | `workspace/notes` と `workspace/my-site` | `"../notes"` |
827 + | CI | `my-site/notes` | `"notes"` |
828 + | Submodule | `my-site/content` | `"content"` |
829 +
830 + たとえば手元では、
831 +
832 + ```text id="b6hw5j"
833 + workspace/
834 + ├─ notes/
835 + └─ my-site/
836 + ```
837 +
838 + なので、
839 +
840 + ```ts id="we09iz"
841 + directory: "../notes"
842 + ```
843 +
844 + となります。
845 +
846 + 一方 CI で、
847 +
848 + ```text id="17ihg4"
849 + my-site/
850 + ├─ app/
851 + ├─ notes/
852 + └─ riebeckite.config.ts
853 + ```
854 +
855 + と Checkout したなら、
856 +
857 + ```ts id="44c17n"
858 + directory: "notes"
859 + ```
860 +
861 + です。
862 +
863 + どちらも **`appRoot` から Content Root への Path** です。
864 +
865 + 可能なら、Local と CI の Layout を揃えておくと設定を単純にできます。
866 +
867 + ## 16. Content image と Attachment は公開方法が異なる
868 +
869 + Vault 内のファイルは3種類に分かれ、公開を担当する場所も異なります。
870 +
871 + | 種類 | 対象 | 公開 URL | 公開の担当 |
872 + | --- | --- | --- | --- |
873 + | Content image | 画像(png、jpg、svg など) | `/<Vault からの相対 logical path>` | build 時に generated output として書き出される |
874 + | Attachment / Media | Markdown でも画像でもないファイル | `/assets/attachments/<Vault からの相対 logical path>` | Site 側の Prebuild |
875 + | Static asset | Site 自身が管理するファイル | `/` 配下 | Vite の `public/` |
876 +
877 + Content image に Site 側の作業は不要です。`obsidianMarkdown()` が公開ページから参照されている image を logical path を保ったまま build の出力に書き出します。開発サーバーでも同じ logical path のまま Content から配信されます。参照されていない image、非公開ページからの image は書き出されません。
878 +
879 + 一方の attachment / media は、
880 +
881 + ```md id="uuzvg8"
882 + ![[attachments/report.pdf]]
883 + ```
884 +
885 + のような Embed が URL を生成しても、
886 +
887 + **Vault にある File 自体が自動的に Public Directory へコピーされるわけではありません。**
888 +
889 + ```mermaid id="4nfh04"
890 + flowchart LR
891 + Image["content image<br/>assets/logo.png"]
892 + Attach["![[attachments/report.pdf]]"]
893 +
894 + Image -->|"build が書き出す"| Public["Public Asset"]
895 + Attach -->|"URL だけ生成"| Copy["Prebuild Copy"]
896 + Copy --> Public
897 + ```
898 +
899 + 公開する attachment / media は Site 側の Prebuild 処理でコピーします。
900 +
901 + Riebeckite Repository では、
902 +
903 + ```text id="zg82a6"
904 + apps/web/scripts/build_images.ts
905 + ```
906 +
907 + が参照実装です。
908 +
909 + `prebuild` から、
910 +
911 + ```text id="i8x6l5"
912 + tsx scripts/build_images.ts
913 + ```
914 +
915 + として実行します。
916 +
917 + ## 17. 公開する Asset だけをコピーする
918 +
919 + Vault 全体を `public/` へコピーするのは避けてください。
920 +
921 + 参照実装では、
922 +
923 + 1. `contentRoot` 内の画像・Attachment を調べる
924 + 2. `ContentManager` で公開 Content を Build する
925 + 3. 公開 Note から参照されている Asset を集める
926 + 4. 必要な Asset だけ Public Directory へコピーする
927 + 5. Public 側に残った不要な Attachment を削除する
928 +
929 + という流れになります。
930 +
931 + Content image は build が公開対象を判断して書き出すため、この Prebuild 処理は主に attachment / media を担当します。参照実装は画像も `public/` へ写しますが、これは初回 build 前の開発サーバーでも配信できるようにするためです。
932 +
933 + ```mermaid id="44q02p"
934 + flowchart TD
935 + Vault["Vault Assets"]
936 + PublicNotes["公開Note"]
937 + Referenced["参照されているAsset"]
938 + Copy["Copy"]
939 + Public["Public Assets"]
940 +
941 + Vault --> Referenced
942 + PublicNotes --> Referenced
943 + Referenced --> Copy
944 + Copy --> Public
945 + ```
946 +
947 + つまり、
948 +
949 + ```text id="a01vl2"
950 + Vaultに存在する
951 + → コピー
952 + ```
953 +
954 + ではなく、
955 +
956 + ```text id="fzcy4e"
957 + 公開Noteから参照されている
958 + → コピー
959 + ```
960 +
961 + です。
962 +
963 + Vault 全体をコピーすると、
964 +
965 + - 非公開 Note からの image
966 + - 非公開 Note の Attachment
967 + - 未使用 Attachment
968 + - `.obsidian` Metadata
969 +
970 + などを誤って公開する可能性があります。
971 +
972 + ## 18. Asset の Public URL
973 +
974 + Content image は Vault からの Logical Path をそのまま保ちます。
975 +
976 + ```text id="9tix0h"
977 + Vault:
978 + assets/logo.png
979 +
980 + Public URL:
981 + /assets/logo.png
982 + ```
983 +
984 + Attachment / Media には専用の Prefix が使われるため、同じ論理 path に画像があっても衝突しません。
985 +
986 + ```text id="6zxxa5"
987 + /assets/attachments/<Vaultからの相対logical path>
988 + ```
989 +
990 + たとえば、
991 +
992 + ```text id="6zxxa5b"
993 + Vault:
994 + attachments/report.pdf
995 +
996 + Public URL:
997 + /assets/attachments/attachments/report.pdf
998 + ```
999 +
1000 + のように、Vault Root からの Logical Path を基準にします。
1001 +
1002 + `attachment()` は解決済み Vault Root から File Size を読み、Root 外への Path を拒否します。
1003 +
1004 + `media()` も同じ Logical Path を使って Audio / Video を描画します。
1005 +
1006 + Public Directory 上の物理配置と URL の対応を揃えておくことで、Build 後の 404 を避けやすくなります。
1007 +
1008 + ## 19. 公開境界を考える
1009 +
1010 + Repository を Private にすることと、Riebeckite で何を公開するかは別の問題です。
1011 +
1012 + ```text id="m8k1eg"
1013 + Repository Visibility
1014 + → Git Repositoryを誰が読めるか
1015 +
1016 + Riebeckite Publication
1017 + → Siteに何を出すか
1018 + ```
1019 +
1020 + Private Vault を利用していても、Build 時に誤って非公開情報を Public Output へコピーすれば公開されてしまいます。
1021 +
1022 + そのため、
1023 +
1024 + ```text id="fvv40k"
1025 + exclude
1026 + +
1027 + publishStrategy
1028 + +
1029 + 公開Noteだけを対象にしたAsset Copy
1030 + ```
1031 +
1032 + の3つを揃えて考えます。
1033 +
1034 + 特に Asset は Vault 全体をそのまま `public/` へコピーしないようにしてください。
1035 +
1036 + 公開ノートから未公開ノートへの参照は、`@riebeckite/plugin-diagnostics` の `riebeckite-diagnostics` で `publish-boundary` 警告として確認できます。`runDiagnostics()` を使うと、同じ診断をプログラムからも取得できます。詳細は [Diagnostics](../../plugins/diagnostics.ja.md) を参照してください。
1037 +
1038 + ## 20. 検証する
1039 +
1040 + Root、Content、公開境界を確認するときは、次の順番で調べます。
1041 +
1042 + ```sh id="w4tr7v"
1043 + npm exec riebeckite check
1044 + npm exec riebeckite doctor
1045 + npm exec riebeckite inspect config
1046 + npm exec -- riebeckite inspect content --list
1047 + npm exec riebeckite inspect graph
1048 + npm exec riebeckite build
1049 + ```
1050 +
1051 + ### `check`
1052 +
1053 + ```sh id="9j1jjc"
1054 + npm exec riebeckite check
1055 + ```
1056 +
1057 + Config と Plugin Contract を検証します。
1058 +
1059 + ### `doctor`
1060 +
1061 + ```sh id="cxapj6"
1062 + npm exec riebeckite doctor
1063 + ```
1064 +
1065 + 読み込めない Content Source や、不正な Filesystem Content Source などを確認します。
1066 +
1067 + ### `inspect config`
1068 +
1069 + ```sh id="ph34ym"
1070 + npm exec riebeckite inspect config
1071 + ```
1072 +
1073 + まずここで、
1074 +
1075 + ```text id="lv98pb"
1076 + Directory
1077 + Publishing
1078 + Exclude
1079 + ```
1080 +
1081 + を確認します。
1082 +
1083 + `Directory` は解決済みの絶対 Path です。
1084 +
1085 + ここで Riebeckite が本当に目的の Vault を見ているか確認します。
1086 +
1087 + ### `inspect content --list`
1088 +
1089 + ```sh id="v3e8bp"
1090 + npm exec -- riebeckite inspect content --list
1091 + ```
1092 +
1093 + 期待している Logical Path が Content として読み込まれているか確認します。
1094 +
1095 + WikiLink や Embed を調査する前に、まず Content 自体が存在するか確認してください。
1096 +
1097 + ### `inspect graph`
1098 +
1099 + ```sh id="ll3gqv"
1100 + npm exec riebeckite inspect graph
1101 + ```
1102 +
1103 + 除外したはずの Note が Graph に残っていないか確認できます。
1104 +
1105 + ### `build`
1106 +
1107 + 最後に、
1108 +
1109 + ```sh id="1f83u0"
1110 + npm exec riebeckite build
1111 + ```
1112 +
1113 + で Integration と Route Rendering を含む実際の Build を確認します。
1114 +
1115 + ```mermaid id="u9uxqa"
1116 + flowchart LR
1117 + Check["check"]
1118 + Doctor["doctor"]
1119 + Config["inspect config"]
1120 + Content["inspect content"]
1121 + Graph["inspect graph"]
1122 + Build["build"]
1123 +
1124 + Check --> Doctor
1125 + Doctor --> Config
1126 + Config --> Content
1127 + Content --> Graph
1128 + Graph --> Build
1129 + ```
1130 +
1131 + ## 21. Working Directory に依存していないか確認する
1132 +
1133 + Root Resolution の問題を調べる場合は、Site Root だけでなく Nested Directory から CLI を実行してみる方法もあります。
1134 +
1135 + たとえば、
1136 +
1137 + ```sh id="kps5t4"
1138 + cd site/app
1139 +
1140 + npm exec riebeckite inspect config
1141 + npm exec -- riebeckite inspect content --list
1142 + ```
1143 +
1144 + としても同じ Application / Vault が解決されることを確認します。
1145 +
1146 + ただし、無関係な Directory から実行した場合は Config 自体を発見できないことがあります。
1147 +
1148 + ## トラブルシューティング
1149 +
1150 + | 症状 | 確認すること |
1151 + | --- | --- |
1152 + | 記事が表示されない | `publish: true`、`content.exclude`、`inspect content --list` |
1153 + | `doctor` が Content Source を報告する | `inspect config` で解決済み Directory を確認 |
1154 + | `Could not find riebeckite.config.*` | CLI を Site の外から実行していないか |
1155 + | `Found multiple Vite applications` | `vite.config.*` が複数ないか |
1156 + | CI で Vault が見つからない | Content の追加 Checkout または Submodule |
1157 + | Private Vault を Checkout できない | Read Token と権限 |
1158 + | Submodule が CI にない | `submodules: recursive` |
1159 + | Submodule の記事が古い | Site 側の Submodule Commit を更新 |
1160 + | Deploy 後に画像が 404 | 公開 Note からの参照と build の出力 |
1161 + | Deploy 後に Attachment が 404 | Prebuild Copy と Public Asset Path |
1162 + | Vault にある画像がコピーされない | 参照元 Note が公開対象か |
1163 + | Local では動くが CI では Path が違う | `appRoot` と Checkout 先 |
1164 + | `exclude` が効かない | Pattern の Anchor と `**/` |
1165 +
1166 + ## よくある Path の問題
1167 +
1168 + Content が見つからない場合は、まず、
1169 +
1170 + ```text id="pyg3ml"
1171 + 「今どこからCommandを実行しているか」
1172 + ```
1173 +
1174 + ではなく、
1175 +
1176 + ```text id="87zq5k"
1177 + 「RiebeckiteがどのappRootを解決したか」
1178 + ```
1179 +
1180 + を確認します。
1181 +
1182 + そのために、
1183 +
1184 + ```sh id="znwr0s"
1185 + npm exec riebeckite inspect config
1186 + ```
1187 +
1188 + を利用します。
1189 +
1190 + 相対 `content.directory` の基準は `appRoot` です。
1191 +
1192 + ```text id="cwrpf3"
1193 + process.cwd()
1194 + ×
1195 +
1196 + configRoot
1197 + ×
1198 +
1199 + appRoot
1200 + ○
1201 + ```
1202 +
1203 + ## よくある CI の問題
1204 +
1205 + CI の問題は、
1206 +
1207 + ```text id="9trzt9"
1208 + Workflowが起動しない
1209 + ```
1210 +
1211 + のか、
1212 +
1213 + ```text id="uj6ym6"
1214 + Workflowは起動するがContentがない
1215 + ```
1216 +
1217 + のかを最初に分けます。
1218 +
1219 + ```mermaid id="wvxw1v"
1220 + flowchart TD
1221 + Problem["記事をPushしてもDeployされない"]
1222 +
1223 + Problem --> Running{"Site Workflowは<br/>起動した?"}
1224 +
1225 + Running -->|"No"| Dispatch["repository_dispatch /<br/>SITE_DISPATCH_TOKEN"]
1226 + Running -->|"Yes"| Content{"Contentは<br/>Checkoutできた?"}
1227 +
1228 + Content -->|"No"| Token["RIEBECKITE_CONTENT_READ_TOKEN /<br/>checkout設定"]
1229 + Content -->|"Yes"| Build["Build Logを確認"]
1230 + ```
1231 +
1232 + この2つは別の仕組みなので、問題を切り分けて確認してください。
1233 +
1234 + ## まとめ
1235 +
1236 + Content と Site を分離するときは、4つの境界を分けて考えると整理しやすくなります。
1237 +
1238 + ```mermaid id="21yk7i"
1239 + flowchart TD
1240 + Storage["1. Repository<br/>どこに保存する?"]
1241 + Source["2. Content Source<br/>どこから読む?"]
1242 + Trigger["3. Deployment Trigger<br/>いつBuildする?"]
1243 + Publication["4. Publication<br/>何を公開する?"]
1244 +
1245 + Storage --> Source
1246 + Source --> Trigger
1247 + Trigger --> Publication
1248 + ```
1249 +
1250 + それぞれ、
1251 +
1252 + ```text id="t4vkse"
1253 + Repository
1254 + → ContentとSiteをどこに保存するか
1255 +
1256 + content.directory
1257 + → RiebeckiteがどこからContentを読むか
1258 +
1259 + Checkout / repository_dispatch
1260 + → CIでどう取得し、いつBuildするか
1261 +
1262 + publishStrategy / exclude / Asset Copy
1263 + → 何をPublic Siteへ出すか
1264 + ```
1265 +
1266 + を担当します。
1267 +
1268 + 特に、
1269 +
1270 + ```text id="77cb3h"
1271 + Content RepositoryをPrivateにする
1272 + ≠
1273 + 自動的に公開境界が安全になる
1274 + ```
1275 +
1276 + という関係に注意してください。
1277 +
1278 + Private Vault を利用する場合でも、`publishStrategy`、`exclude`、Asset Copy のすべてで Public Boundary を維持してください。
1279 +
1280 + ### 関連資料
1281 +
1282 + - [記事とサイトのリポジトリ分離](../content-repositories.ja.md) — 分離構成を最初から作る
1283 + - [Configuration](../../reference/configuration.ja.md) — Root Resolution と外部 Vault
1284 + - [利用ガイド](../README.ja.md) — Content / Asset の基本的な扱い
1285 + - Cloudflare デプロイテンプレート — Deployment Workflow
1286 +
1287 +