今最もクールな ブルーグラスサイトとして 注目されているのが Bluemoon だ
新システム構想(#212)に基づき、Astro + Content Collectionsで構築中。
src/
├── pages/
│ ├── index.astro # トップページ
│ └── events/[slug].astro # 年度イベントページ(コンテンツコレクションから生成)
├── content/events/ # 年度ごとのイベント内容(Markdown/MDX)
├── layouts/ # BaseLayout / EventLayout
├── components/ # SiteNav, InfoCard, ScheduleTable 等
├── assets/ # ビルド時最適化される画像
└── styles/global.css
public/ # そのまま配信される静的ファイル(favicon等)
本番はCloudflare Workers Static AssetsのWorker bluemoonから、src/のAstroビルド成果物(dist/)を配信します。
npm install
npm run dev # http://localhost:4321 でプレビュー
npm run check # 型・テンプレートチェック
npm run build # dist/ に静的ビルド
npm run preview # ビルド結果をローカルで確認新しい年度イベントページを追加する場合は src/content/events/ に .mdx ファイルを1つ追加するだけで /events/<slug> に自動生成されます。
https://www.bluemoon.works/admin/ にSveltia CMSを配置しています。events / news / pages コレクションを管理でき、GitHub OAuthでログインして変更をGitHubリポジトリへcommitします。
CMSの書き込み先は cms-drafts ブランチです。公開操作があると .github/workflows/cms-publish.yml が検証(npm run check / npm run build / 変更パスがコンテンツパスのみか検査)後にmainへ自動マージし、main ブランチのデプロイCIが本番へ反映します。このため、編集担当者はブランチやPRを意識せずに公開できます。
cms-drafts への変更は コンテンツパス(src/content/** / src/assets/events/** / public/images/**)のみが公開フローを通ります。それ以外のパス(コード・設定等)の変更が含まれるpushは公開ワークフローが失敗し、本番には反映されません(GitHub Freeではルールセットのパス制限が使えないため、CI層で検査しています。ブランチの削除・force-pushはルールセットで禁止)。コード変更はこれまでどおりPR経由です。
- CMS設定:
public/admin/config.yml - CMS本体:
public/admin/index.html(CDNから読み込み) - OAuth Worker:
workers/sveltia-cms-auth/(GitHubログイン処理用)
編集者のアカウントを追加するには、GitHubでリポジトリ azumag/bluemoon にそのユーザーを collaborator として追加してください。
編集者向けの更新手順は docs/editors-manual.md を参照してください。
cms-publish.yml がmainへマージするには、以下のActions Secretが必要です。mainのルールセット(copilot review)をバイパスできる権限が必要なため、GitHub App(例: bluemoon-cms-bot)を使用します(PATは2025年以降のGitHub仕様でルールセットをバイパスできません)。
- GitHub App作成(Webhook無効、Permissions: Contents → Read and write、対象リポジトリ
azumag/bluemoonのみ)し、リポジトリへインストール - リポジトリのルールセット「copilot review」のバイパスリストにそのAppを追加(設定済み)
- Secretsに登録:
CMS_APP_ID: GitHub AppのApp IDCMS_APP_PRIVATE_KEY: GitHub Appのprivate key(PEMの全文)
- eventsのヒーロー画像:
src/assets/events/にアップロードされ、Astroで最適化されます - news/pages本文の画像:
public/images/にアップロードされ、/images/...として配信されます
public/images/ が存在しない場合は空の .gitkeep を置いてください。
mainブランチにpushすると、.github/workflows/main.ymlが次の順で実行され、Cloudflare Workersのbluemoon(本番)とsveltia-cms-auth(CMS認証)へデプロイします。
npm ci → npm run check → npm run build → wrangler deploy (bluemoon)
→ wrangler deploy (sveltia-cms-auth)
Node.jsのバージョンはリポジトリ直下の.node-version、Wranglerのバージョンはpackage.jsonのdevDependencies(package-lock.jsonで依存ごと固定)が正本です。全workflow(本番・PRプレビュー・CMS公開・CMS認証Worker)が同じ値を使うため、更新するときはこの2か所だけを変更します。
sveltia-cms-auth Workerの初回セットアップ:
- GitHubの
https://github.com/settings/applications/newでOAuthアプリを登録。Authorization callback URLはhttps://sveltia-cms-auth.tsubasa-azumagakito.workers.dev/callback - 発行されたClient IDは
workers/sveltia-cms-auth/wrangler.tomlの[vars]のGITHUB_CLIENT_IDに書く(認可URLに載る公開値のため)。Client SecretだけをWorkerのSecretとして登録(ダッシュボードのsveltia-cms-auth→ Settings → Variables で「Encrypt」、または下記コマンド)npx wrangler secret put GITHUB_CLIENT_SECRET --config workers/sveltia-cms-auth/wrangler.toml
- ダッシュボードの通常変数(plain text)は使わないこと。
wrangler deployはkeep_varsなしではダッシュボードの通常変数を毎回消すため、mainへのpushごとに消えてCMSログインがMISCONFIGURED_CLIENTになる ALLOWED_DOMAINS(必須。認証エンドポイントを使えるサイトの制限)はworkers/sveltia-cms-auth/wrangler.tomlの[vars]で管理する。値はwww.bluemoon.works。Sveltia CMSはsite_idにポートなしのhostnameを送る(localhostではcms.netlify.com)ため、ローカルのnpm run devではGitHubログインではなくローカルリポジトリ機能を使う
- ダッシュボードの通常変数(plain text)は使わないこと。
初回だけ、GitHubリポジトリのActions SecretにCLOUDFLARE_API_TOKENを登録してください。Cloudflareダッシュボードで**アカウントレベル(全Workerスクリプト)**のデプロイ権限を許可したAPIトークンを作成します。bluemoonとsveltia-cms-authの両方にデプロイするため、特定スクリプト名限定のトークンではsveltia-cms-authのデプロイが失敗します。値をGitHubのSettings → Secrets and variables → Actionsに保存します。アカウントIDはwrangler.tomlのaccount_idを使用します。
ローカルからデプロイする場合は、Cloudflare OAuthログイン済みの環境で次を実行します。
npm ci
npm run check
npm run build
npx wrangler deploy
# CMS認証Worker
npx wrangler deploy --config workers/sveltia-cms-auth/wrangler.tomlPull Requestを作成または更新すると、.github/workflows/pr-preview.ymlがPR番号ごとにbluemoon-pr-<番号>というWorkerを作成し、workers.devのプレビューURLをPRへコメントします。PRをクローズすると、そのプレビューWorkerを削除します。