# 配布と更新のしくみ

> サーバーは編集しない npm パッケージで届き、Webサイトはあなたが編集します。更新は Dependabot の PR で届きます。

このページでは、どのファイルをご自身で編集するか、更新がどう届くか、どの [PR](https://goodnight-web.dev/ja/docs/glossary.md#pull-request) をマージしてよいかがわかります。問い合わせ API と管理画面は npm パッケージで届き、あなたは編集しません。Astro で作るWebサイトは、あなたが自由に編集します。

## 編集する人で 2 つに分ける

|            | `@goodnight-web/server`（npm パッケージ）                                                                                                                          | あなたのサイト（starter）                                                                                                       |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| 編集する人 | goodnight-web の開発者。あなたは編集しません。                                                                                                                     | あなた、またはあなたが使う AI ツール                                                                                            |
| 中身       | Worker のファクトリー、問い合わせ API、Better Auth と Passkey、管理 API、日本語と英語の文言を含むビルド済みの管理画面、セキュリティヘッダー、D1 のマイグレーション | Astro のページ、Markdown のコンテンツ（日本語と英語）、`site.config.ts`、スタイル、`wrangler.jsonc`、3 行ほどの `src/worker.ts` |
| 変わり方   | semver に従った新しいバージョン                                                                                                                                    | あなた自身のコミット                                                                                                            |

starter の `src/worker.ts` は、Worker を組み立てる関数（ファクトリー）を呼ぶだけです。サーバーのコードは、パッケージの中にとどまります。

starter の `AGENTS.md` と `CLAUDE.md` は、AI ツールに次のことを伝えます。

- 編集してよいもの: `src/pages`、`src/content`、`site.config.ts`、`src/styles`
- 触らないもの: ファクトリーの呼び出し、binding の名前、`@goodnight-web/*`

### 色

サイトと管理画面の色は別々です。サイトの色を変えても管理画面は変わらず、パッケージを更新してもサイトの色は変わりません。

- **サイト:** 色は starter にあります。主な 3 色は `site.config.ts` の `theme`、そこから混ぜて作る色は `src/styles/global.css` です。サンプルのサイトは緑です。
- **管理画面（`/admin`）:** パッケージの中に、独自の固定テーマがあります。ニュートラルな背景に紫系のメインカラーで、ライトとダークのモードがあります。

## コピーの作り方

Deploy to Cloudflare ボタンは、公開されている goodnight-web リポジトリの `templates/starter` フォルダーを、**あなたの GitHub アカウントの新しいリポジトリ**にコピーします。fork ではなく、コピーです。

あなたのコピーは、goodnight-web リポジトリの変更を追いかけません。サーバーのコードは、npm パッケージを通して届きます。手順は[はじめかた](https://goodnight-web.dev/ja/docs/getting-started.md)を参照してください。

### lockfile [#lockfile]

starter には `package-lock.json` が入っています。サイトがインストールするすべてのパッケージについて、正確なバージョンとチェックサムを記録したファイルです。Workers Builds はこのファイルから `npm ci` でインストールします。そのため、最初のデプロイではコミットされたとおりのパッケージが入り、その後のビルドも、PR がこのファイルを変えるまで同じパッケージを使います。

- Dependabot は、`package.json` と `package-lock.json` を一緒に更新します。
- `package.json` のバージョンを手で書き換えるときは、lockfile も更新してください。`npm install`（または `npm install <パッケージ>@<バージョン>`）を実行し、2 つのファイルを一緒にコミットします。2 つが合っていないと、`npm ci` が止まり、ビルドが失敗します。
- `package-lock.json` を手で編集しないでください。

## リポジトリは private にする [#private-repository]

サイトのコピーは、**private** リポジトリに置いてください。GitHub では **Settings > General > Danger Zone > Change visibility** を開き、private を選びます。

public リポジトリでは、サイトのソース全体、設定、すべての変更の履歴が誰にでも見えます。間違えてコミットしたものも見えます。また、知らない人が PR を作れます。これは、[更新チェックのワークフロー](#the-update-check-workflow)が警告する状況そのものです。

starter の `.github/workflows/visibility-check.yml` は、push のたびに動きます。手動でも実行できます。リポジトリが public なら、「private にしてください」というエラーを日本語と英語で出し、ジョブを失敗させます。

このワークフローには、トークンも権限もいりません。このワークフローは starter フォルダーの中にあるため、あなたのコピーでだけ動きます。

## 更新の届き方

1. goodnight-web が `@goodnight-web/server` の新しいバージョンを公開します。脆弱性の修正なら、GitHub の Security Advisory も出します。
2. [Dependabot](https://goodnight-web.dev/ja/docs/glossary.md#dependabot) が、公開から 5 日たったバージョンを提案します。設定は starter の `.github/dependabot.yml` にあります。
   - `@goodnight-web/*` の更新は、1 つの PR にまとめます。
   - セキュリティアップデートは、この 5 日の待ち（cooldown）なしで届きます。
   - サイトのほかのパッケージ（Astro など）のマイナーとパッチの更新は、別のグループにまとめます。

   ```yaml title="templates/starter/.github/dependabot.yml"
   cooldown:
     default-days: 5
   groups:
     goodnight-web:
       patterns: ['@goodnight-web/*']
     site-dependencies:
       patterns: ['*']
       exclude-patterns: ['@goodnight-web/*']
       update-types: [minor, patch]
   ```

3. Dependabot が、あなたのリポジトリに PR を 1 つ作ります。変更するのは `package.json` と `package-lock.json` です。
4. Workers Builds が本番以外のブランチもビルドする設定（Cloudflare ダッシュボードの、Worker のビルド設定）になっていれば、その PR をビルドし、ビルドが通ったかどうかを表示します。公開のアドレスは作られません。starter はプレビュー URL を無効にしているからです（`wrangler.jsonc` の `"preview_urls": false`）。
5. PR を確認します。見るのは、更新チェックのコメントとステータス、ビルドの結果（あれば）、変更内容です。マージする前に試したいときは、PR のブランチを手元に取り出し、`npm run preview` を実行します。確認が済んだらマージします。`deploy` スクリプトは `wrangler d1 migrations apply DB --remote && wrangler deploy` を実行します。新しいコードが動く前に、データベースのマイグレーションが済みます。

プレビュー URL を無効にしているのは、プレビューの各バージョンが、マージ前の状態のまま、本番の D1 データベースとシークレットを使って公開のアドレスで動くからです。必要な場合は、そのことを理解したうえで `"preview_urls": true` にしてください。

管理画面からのワンクリック更新はありません。ワンクリックで更新するには、あなたの GitHub リポジトリに書き込めるトークンを Worker に持たせる必要があります。goodnight-web は、Worker にその権限を持たせません。

### 更新チェックのワークフロー [#the-update-check-workflow]

**公式のアップデートのコメントが付き、`goodnight-web/update-check` のチェックが成功している PR だけをマージしてください。** 「update」と書かれた PR が、goodnight-web からの更新とは限りません。

starter の `.github/workflows/update-check.yml` は、すべての PR にコメントを付けます。コメントは日本語と英語です。新しいコメントを増やさず、同じコメントを更新します。あわせて、PR の最新のコミットに `goodnight-web/update-check` というコミットステータスを付けます。成功になるのは公式のアップデートのときだけです。コメントの文面は誰でも PR に貼り付けられますが、ステータスを付けるにはリポジトリへの書き込み権限が必要です（このワークフローのトークンにはあります）。ステータスは 1 つのコミットに結び付きます。ステータスの動作は、まだ GitHub 上で確かめていません。

| 判定               | コメント                                                                                                                               |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| 公式のアップデート | 「goodnight-web の公式アップデートです」「マージして問題ありません。ビルドの結果が表示されていれば、確認してからマージしてください。」 |
| それ以外           | 「公式のアップデートではありません」「変更内容がわからない場合は、マージしないでください。」                                           |

#### 公式とみなす条件

次のすべてを満たす PR だけを、**公式のアップデート**とみなします。

- 作成者が `dependabot[bot]` で、アカウントの種類が `Bot` である。
- ブランチが fork ではなく、あなた自身のリポジトリにある。
- PR のすべてのコミットの作成者とコミッターが `dependabot[bot]` で、GitHub が署名を検証済みと表示している。ほかの人が Dependabot のブランチに push したコミットや、GitHub の画面で作ったコミットがあると、公式とみなしません。
- 変更されたファイルが `package.json` と lockfile（`bun.lock`、`package-lock.json`、`pnpm-lock.yaml`）だけである。どれも追加や削除ではなく、変更である。
- `package.json` で変更された行が、すべて `@goodnight-web/*` の依存関係のバージョンである。

lockfile は、ほかの部分が変わってもかまいません。`@goodnight-web/server` を更新すると、それが使うパッケージ（たとえば `hono`）のバージョンも上がることがあります。その変更は lockfile にだけ現れます。ただし、`package-lock.json` に加わるパッケージは、すべて `https://registry.npmjs.org/` から取得し、sha512 のチェックサムを持つものでなければなりません。Git の URL、手元のパス、リンク、`http://`、ほかのホストがあると、公式とみなしません。

サイトが直接使うパッケージは、`package.json` に書かれています。こちらの差分は 1 行ずつ確認します。

すべての変更を確認できない PR は、公式とみなしません。たとえば、GitHub が `package.json` や `package-lock.json` の差分を省略したり途中で切ったりした場合や、変更ファイルやコミットの一覧が欠けているおそれがある場合です。

#### スクリプトの中身

スクリプト `.github/scripts/update-check.cjs` は、満たさない条件ごとに理由を集めます。理由が 1 つもないときだけ、公式とみなします。最初の 2 つの条件は次のとおりです。

```js title="templates/starter/.github/scripts/update-check.cjs"
const user = pullRequest.user;
if (!user || user.login !== OFFICIAL_AUTHOR || user.type !== 'Bot') {
  reasons.push(
    `author is not ${OFFICIAL_AUTHOR}: ${user ? clean(user.login) : 'unknown'}`,
  );
}

const headRepo = pullRequest.head.repo
  ? pullRequest.head.repo.full_name
  : null;
if (headRepo !== pullRequest.base.repo.full_name) {
  reasons.push(
    `branch is not in this repository: ${headRepo === null ? 'deleted repository' : clean(headRepo)}`,
  );
}
```

続いて、`package.json` で変更された行が、すべて `@goodnight-web/*` の項目で、バージョン番号を指定しているかを確かめます。

```js title="templates/starter/.github/scripts/update-check.cjs"
for (const change of packageJsonChanges(hunks)) {
  if (change.name === null) {
    reasons.push(
      `package.json: change outside a dependency entry: ${clean(change.line)}`,
    );
  } else if (!change.name.startsWith(OFFICIAL_SCOPE)) {
    reasons.push(
      `package.json: entry other than an ${OFFICIAL_SCOPE}* dependency changed: ${clean(change.name)}`,
    );
  } else if (change.invalidSpec) {
    reasons.push(
      `package.json: ${clean(change.name)} is not set to a version number: ${clean(change.line)}`,
    );
  } else {
    officialChanges += 1;
  }
}
```

#### PR のコードは動かさない

このワークフローは `pull_request_target` で動くので、コメントを書く権限があります。ただし、PR のコードをチェックアウトすることも、実行することもありません。

ベースブランチから `.github/scripts` だけをチェックアウトします。動くのは、すでにあなたのリポジトリにあるスクリプトです。PR については、GitHub API でメタデータと変更ファイルの一覧だけを読みます。

```yaml title="templates/starter/.github/workflows/update-check.yml"
steps:
  - name: Check out the script from the base branch
    uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
    with:
      ref: ${{ github.event.pull_request.base.sha }}
      persist-credentials: false
      sparse-checkout: .github/scripts
  - name: Classify the pull request and comment
    uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
    with:
      script: |
        const { run } = require('./.github/scripts/update-check.cjs');
        await run({ github, context, core });
```
