From c7ca4381c9ba4ec27860488f070bf1479e3f0328 Mon Sep 17 00:00:00 2001 From: tknkaa Date: Fri, 2 Oct 2026 21:40:03 +0900 Subject: [PATCH 01/10] feat: use Worker Previews for branch previews instead of a separate preview Worker --- AGENTS.md | 2 +- apps/web/src/lib/preview-name.test.ts | 37 +++++ apps/web/src/lib/preview-name.ts | 30 ++++ apps/web/wrangler.jsonc | 60 ++++--- apps/web/wrangler.preview-migrations.jsonc | 14 ++ docs/architecture.md | 2 +- docs/deploy.md | 181 ++++++++++----------- docs/development.md | 16 +- package.json | 7 +- scripts/deploy-preview.sh | 20 +++ scripts/preview-name.ts | 9 + 11 files changed, 235 insertions(+), 143 deletions(-) create mode 100644 apps/web/src/lib/preview-name.test.ts create mode 100644 apps/web/src/lib/preview-name.ts create mode 100644 apps/web/wrangler.preview-migrations.jsonc create mode 100755 scripts/deploy-preview.sh create mode 100644 scripts/preview-name.ts diff --git a/AGENTS.md b/AGENTS.md index 1723cdf..fafb833 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ pnpm check # フォーマット、lint、型チェック、テ pnpm test:e2e # 本物のブラウザでの E2E (Chrome が必要。空の DB で別のポート 8788 に起動する) pnpm format # Oxfmt で整形 pnpm test # Vitest (apps/web、apps/reminder-worker、packages/db) -pnpm check:deploy # Wrangler の設定 (本番、プレビュー、ワーカー) が読めることの確認。アップロードはしない +pnpm check:deploy # Wrangler の設定 (Web とメールの Worker) が読めることの確認。アップロードはしない pnpm db:generate --name=<内容が分かる名前> # マイグレーションを生成 pnpm db:migrate:local # ローカルの D1 に適用 pnpm dev # ビルドして wrangler dev (http://localhost:8787) diff --git a/apps/web/src/lib/preview-name.test.ts b/apps/web/src/lib/preview-name.test.ts new file mode 100644 index 0000000..fb38f7a --- /dev/null +++ b/apps/web/src/lib/preview-name.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest' +import { previewName, previewUrl } from './preview-name' + +describe('previewName', () => { + it.each([ + ['feat/worker-previews', 'feat-worker-previews'], + ['Fix/Drop_On_Card', 'fix-drop-on-card'], + ['docs/audit--docs/', 'docs-audit-docs'], + ['123-hotfix', 'b-123-hotfix'], + ['日本語のブランチ', 'preview'], + ['---', 'preview'], + ])('turns %j into %j', (branch, name) => { + expect(previewName(branch)).toBe(name) + }) + + it('keeps the first label of the address within 63 characters', () => { + const name = previewName(`feat/${'a-very-long-branch-name-'.repeat(5)}`) + expect(`${name}-kanban-web`.length).toBeLessThanOrEqual(63) + expect(name).toMatch(/^[a-z][a-z0-9-]*[a-z0-9]$/) + }) + + it('gives two long branches that start the same different names', () => { + const base = `feat/${'x'.repeat(80)}` + expect(previewName(`${base}-one`)).not.toBe(previewName(`${base}-two`)) + }) + + it('always gives the same name for the same branch', () => { + const branch = `feat/${'y'.repeat(80)}` + expect(previewName(branch)).toBe(previewName(branch)) + }) +}) + +describe('previewUrl', () => { + it('is the Preview address on the ut-code workers.dev subdomain', () => { + expect(previewUrl('feat-x')).toBe('https://feat-x-kanban-web.ut-code.workers.dev') + }) +}) diff --git a/apps/web/src/lib/preview-name.ts b/apps/web/src/lib/preview-name.ts new file mode 100644 index 0000000..f170422 --- /dev/null +++ b/apps/web/src/lib/preview-name.ts @@ -0,0 +1,30 @@ +import { createHash } from 'node:crypto' + +const WORKER = 'kanban-web' +// A preview's address is `-kanban-web..workers.dev`; that first label may be at +// most 63 characters. +const MAX_NAME = 63 - WORKER.length - 1 + +/** + * Turns a branch name into a Preview name: lowercase letters, digits and dashes, starting with a + * letter, short enough for the address. A shortened name gets a hash so two long branches that + * start the same do not share a Preview. + */ +export function previewName(branch: string) { + let name = branch + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + if (!/^[a-z]/.test(name)) name = `b-${name}`.replace(/-+$/, '') + if (name === 'b' || name === '') name = 'preview' + + if (name.length > MAX_NAME) { + const hash = createHash('sha1').update(branch).digest('hex').slice(0, 6) + name = `${name.slice(0, MAX_NAME - 7).replace(/-+$/, '')}-${hash}` + } + return name +} + +export function previewUrl(name: string, subdomain = 'ut-code') { + return `https://${name}-${WORKER}.${subdomain}.workers.dev` +} diff --git a/apps/web/wrangler.jsonc b/apps/web/wrangler.jsonc index 4e26089..7c39a40 100644 --- a/apps/web/wrangler.jsonc +++ b/apps/web/wrangler.jsonc @@ -51,45 +51,41 @@ }, ], - // Preview deployments (a branch or a pull request) run as their own Worker with their own - // database, bucket and queue, so trying a change never touches production data. Bindings are - // not inherited by an environment, so each one is written out again. See docs/deploy.md. - "env": { - "preview": { - "name": "kanban-web-preview", - "vars": { - "BETTER_AUTH_URL": "https://kanban-web-preview.ut-code.workers.dev", + // Worker Previews: every branch other than main gets its own Preview of this Worker, at + // `-kanban-web.ut-code.workers.dev`. Durable Objects are separate per Preview on their + // own. D1, R2 and queues would be shared with production unless they are named here, so they + // point at the preview resources (shared by all Previews). BETTER_AUTH_URL differs per Preview + // and is passed by scripts/deploy-preview.sh. See docs/deploy.md. + "previews": { + "d1_databases": [ + { + "binding": "DB", + "database_name": "kanban-db-preview", + "database_id": "3e03d55d-c10d-46fc-8014-9adcec53b7b2", + "migrations_dir": "../../packages/db/migrations", + }, + ], + "r2_buckets": [ + { + "binding": "FILES", + "bucket_name": "kanban-files-preview", }, - "d1_databases": [ + ], + "queues": { + "producers": [ { - "binding": "DB", - "database_name": "kanban-db-preview", - "database_id": "3e03d55d-c10d-46fc-8014-9adcec53b7b2", - "migrations_dir": "../../packages/db/migrations", + "binding": "NOTIFICATION_QUEUE", + "queue": "kanban-notifications-preview", }, ], - "r2_buckets": [ + }, + "durable_objects": { + "bindings": [ { - "binding": "FILES", - "bucket_name": "kanban-files-preview", + "name": "COLLABORATION", + "class_name": "CollaborationRoom", }, ], - "queues": { - "producers": [ - { - "binding": "NOTIFICATION_QUEUE", - "queue": "kanban-notifications-preview", - }, - ], - }, - "durable_objects": { - "bindings": [ - { - "name": "COLLABORATION", - "class_name": "CollaborationRoom", - }, - ], - }, }, }, } diff --git a/apps/web/wrangler.preview-migrations.jsonc b/apps/web/wrangler.preview-migrations.jsonc new file mode 100644 index 0000000..36df654 --- /dev/null +++ b/apps/web/wrangler.preview-migrations.jsonc @@ -0,0 +1,14 @@ +{ + // Only for applying migrations to the preview database, which all Worker Previews share + // (`wrangler d1 migrations apply` does not read the `previews` block of wrangler.jsonc). + "$schema": "../../node_modules/wrangler/config-schema.json", + "name": "kanban-web", + "d1_databases": [ + { + "binding": "DB", + "database_name": "kanban-db-preview", + "database_id": "3e03d55d-c10d-46fc-8014-9adcec53b7b2", + "migrations_dir": "../../packages/db/migrations", + }, + ], +} diff --git a/docs/architecture.md b/docs/architecture.md index 41c2796..a1e4b31 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,7 +2,7 @@ ![構成図](architecture.png) -Cloudflare 上で動く Web アプリ (TanStack Start) と、メール送信用のワーカー (リマインド、共有の通知) の 2 つの Worker で構成する。ブランチや PR の動作確認用に、Web アプリのプレビュー用の Worker もある ([デプロイとプレビュー](deploy.md))。 +Cloudflare 上で動く Web アプリ (TanStack Start) と、メール送信用のワーカー (リマインド、共有の通知) の 2 つの Worker で構成する。ブランチや PR の動作確認には、Web アプリの Worker Previews (ブランチごとのプレビュー) を使う ([デプロイとプレビュー](deploy.md))。 図は大まかなもので、共有の通知用のキュー (`kanban-notifications`) などは描いていない。 diff --git a/docs/deploy.md b/docs/deploy.md index 93b4ff1..913fa54 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -1,132 +1,102 @@ # デプロイとプレビュー -Cloudflare にデプロイする手順。**ここに書いたことのうち、リソースの作成、シークレットの設定、ダッシュボードの設定は、Cloudflare のアカウントが要るので、まだ実際には試していない。** 確認できているのは、Wrangler の設定が正しく読めること (`pnpm check:deploy`、CI でも実行する) まで。 +Cloudflare (ut-code のアカウント `ut.code();`) へのデプロイとプレビュー。デプロイは GitHub の CI ではなく、Cloudflare の **Workers Builds** (ダッシュボードで設定) が行う。 [#3](https://github.com/ut-code/kanban/issues/3) で扱う。 +## いまの状態 + +| | 状態 | +| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| 本番の Web (`kanban-web`) | デプロイ済み: https://kanban-web.ut-code.workers.dev | +| メールを送る Worker (`kanban-reminder-worker`) | デプロイ済み (cron は 15 分ごと)。公開 URL はない (`workers_dev: false`) | +| プレビュー | Worker Previews。ブランチごとに `https://<ブランチ名>-kanban-web.ut-code.workers.dev` | +| リソース | D1 2 個、R2 2 個、キュー 4 つ (下の表)。マイグレーションは本番とプレビューの両方に適用済み | +| シークレット | `BETTER_AUTH_SECRET` を、本番とプレビュー (全プレビュー共通) に設定済み (別の値) | +| Workers Builds | ダッシュボードで設定済み (下の 4 の値) | +| メール (Resend) | **未設定** ([#8](https://github.com/ut-code/kanban/issues/8))。それまで、本番のメールの送信は失敗して、デッドレターキューに溜まる | +| Google ログイン | **未設定** ([#2](https://github.com/ut-code/kanban/issues/2)) | + ## 名前 -Cloudflare のリソースの名前は、**Cloudflare のアカウントの中で一意**になる (GitHub の org ではない)。共有のアカウントで他のプロジェクトとぶつからないよう、すべての名前に `kanban-` を付けている。名前を変えるときは、`wrangler.jsonc` (2 つ)、`apps/reminder-worker/src/index.ts` (通知のキューの名前)、`package.json` と `justfile` と `e2e/start-server.sh` (D1 の名前)、この docs をそろえて直す。 +Cloudflare のリソースの名前は、**Cloudflare のアカウントの中で一意**になる (GitHub の org ではない)。共有のアカウントで他のプロジェクトとぶつからないよう、すべて `kanban-` で始める。 + +| 種類 | 本番 | プレビュー (全プレビューで共有) | +| ------ | --------------------------------------------------------------------- | ------------------------------------------------- | +| Worker | `kanban-web`、`kanban-reminder-worker` | (`kanban-web` の Worker Previews) | +| D1 | `kanban-db` | `kanban-db-preview` | +| R2 | `kanban-files` | `kanban-files-preview` | +| キュー | `kanban-reminders`、`kanban-notifications`、`kanban-mail-dead-letter` | `kanban-notifications-preview` (取り出す側はない) | + +名前を変えるときは、`wrangler.jsonc` (2 つ)、`apps/web/wrangler.preview-migrations.jsonc`、`apps/reminder-worker/src/index.ts` (通知のキューの名前)、`package.json`、`justfile`、`e2e/start-server.sh`、`scripts/deploy-preview.sh` と `apps/web/src/lib/preview-name.ts` (Worker の名前)、この docs をそろえて直す。 ## アカウントの指定 Wrangler が複数のアカウントにログインしているときは、どれを使うかを環境変数で指定する (指定しないと、対話できない場所ではエラーになる)。 ```sh -export CLOUDFLARE_ACCOUNT_ID=<アカウント ID> # `wrangler whoami` で分かる +export CLOUDFLARE_ACCOUNT_ID=df6c3acd32f66bd1eb95e50607684297 # ut.code(); ``` -## 構成 - -| Worker | 設定 | 役割 | -| ------------------------ | ------------------------------------------ | ------------------------------------------------------------------- | -| `kanban-web` | `apps/web/wrangler.jsonc` | 画面、API | -| `kanban-reminder-worker` | `apps/reminder-worker/wrangler.jsonc` | リマインドと共有の通知のメール (cron、Queues) | -| `kanban-web-preview` | `apps/web/wrangler.jsonc` の `env.preview` | ブランチや PR の動作確認用。本番とは別の DB、バケット、キューを使う | - -プレビューにはリマインド用のワーカーを置かない。cron が動いて、本番と同じようにメールを送ってしまうのを避けるため。 - -## いまの状態 (ut-code のアカウント `ut.code();`) - -| | 状態 | -| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| リソース | D1 2 個、R2 2 個、キュー 4 つ。作成済み | -| マイグレーション | 本番とプレビューの両方に、すべて適用済み | -| `kanban-web` (本番) | デプロイ済み: https://kanban-web.ut-code.workers.dev | -| `kanban-web-preview` | デプロイ済み: https://kanban-web-preview.ut-code.workers.dev (別の DB) | -| `kanban-reminder-worker` | デプロイ済み (cron は 15 分ごと)。公開 URL はない (`workers_dev: false`) | -| シークレット | `BETTER_AUTH_SECRET` は、本番とプレビューに設定済み (別の値) | -| デプロイ後の確認 | プレビューで、アカウント作成、タスク、状態の変更、添付 (R2)、共有 (キュー)、WebSocket と Durable Objects を確認済み。本番には、データを作らないよう、アカウントは作っていない | -| Workers Builds (自動デプロイ) | **未設定**。下の 5 を、ダッシュボードで設定する | -| メール (Resend) | **未設定** ([#8](https://github.com/ut-code/kanban/issues/8))。それまで、メールの送信は失敗して、デッドレターキューに溜まる | -| Google ログイン | **未設定** ([#2](https://github.com/ut-code/kanban/issues/2)) | - -## 1. リソースを作る (最初の 1 回) - -Wrangler にログインする (`pnpm --filter @todo/web exec wrangler login`)。 +## 1. 本番 (`main`) -```sh -cd apps/web -# 本番 -pnpm exec wrangler d1 create kanban-db # 出てきた database_id を、下の 2 つの設定に書く -pnpm exec wrangler r2 bucket create kanban-files -pnpm exec wrangler queues create kanban-reminders -pnpm exec wrangler queues create kanban-notifications -pnpm exec wrangler queues create kanban-mail-dead-letter # 再試行しても送れなかったメールの置き場 +- `main` に入ると、Workers Builds が、本番の D1 にマイグレーションを適用してから、`kanban-web` をデプロイする。 +- **merge しただけでは、マイグレーションは走らない。** Workers Builds のデプロイコマンドが `d1 migrations apply` を実行して、初めて適用される。手元からは `pnpm db:migrate:remote`。 +- マイグレーションはデプロイより先に走る。列の追加はそのままでよいが、列の削除や名前の変更は、古いコードが壊れるので、2 回に分けて出す。 +- `kanban-reminder-worker` は、別の Workers Builds のプロジェクトとしてデプロイする。 -# プレビュー -pnpm exec wrangler d1 create kanban-db-preview # database_id を apps/web/wrangler.jsonc の env.preview に書く -pnpm exec wrangler r2 bucket create kanban-files-preview -pnpm exec wrangler queues create kanban-notifications-preview -``` +## 2. プレビュー (`main` 以外のブランチ) -`database_id` を書く場所 (ut-code のアカウントに作成済みで、書き込み済み): +[Worker Previews](https://developers.cloudflare.com/workers/previews/) を使う。**同じ Worker (`kanban-web`) のまま**、ブランチごとに独立したプレビューができる。 -- `apps/web/wrangler.jsonc` の `d1_databases` (本番) と `env.preview.d1_databases` (プレビュー) -- `apps/reminder-worker/wrangler.jsonc` の `d1_databases` (本番。リマインドの対象を探すため、Web と同じ DB を指す) +- URL はブランチごとに固定: `https://<プレビュー名>-kanban-web.ut-code.workers.dev`。プレビュー名は、ブランチ名を小文字、数字、`-` にしたもの (`apps/web/src/lib/preview-name.ts`。例: `feat/worker-previews` → `feat-worker-previews`)。同じブランチにプッシュすると、そのプレビューが更新される。複数のブランチを同時に見られる。 +- **Durable Objects** は、プレビューごとに別の名前空間が自動で作られる。 +- **D1、R2、キュー**は、何も指定しないと本番と共有されてしまうので、`wrangler.jsonc` の `previews` ブロックで、プレビュー用のものを指定している。**D1 と R2 は全プレビューで 1 つを共有する** (データも共有)。列の削除や名前の変更を含むブランチを出すと、他のブランチのプレビューが壊れることがある。壊れたら、プレビュー用の DB を作り直す。 +- **ログイン:** ログインは `BETTER_AUTH_URL` と同じオリジンからのリクエストしか受け付けない。プレビューごとに URL が違うので、`scripts/deploy-preview.sh` が、`wrangler preview --name <プレビュー名> --var BETTER_AUTH_URL:<そのプレビューの URL>` で、プレビューごとに渡している。 +- **メール:** キューを取り出す側 (コンシューマ) と cron は、プレビューでは動かない (Cloudflare の制約)。プレビューでは、メールは送られない。 +- 1 つの Worker に、プレビューは 100 個まで (無料プラン)。超えると、古いものから自動で削除される。手で消すときは `wrangler preview delete --name <プレビュー名>`。 +- Worker Previews はオープンベータ (2026 年 9 月に公開)。 -## 2. データベースを用意する - -```sh -pnpm db:migrate:remote # 本番 -pnpm db:migrate:preview # プレビュー -``` +手元からプレビューを作る: `pnpm deploy:preview` (いまのブランチを、そのブランチ名のプレビューにする)。 -マイグレーションを足したら、デプロイの前に適用する (下の Workers Builds では、デプロイのコマンドに含める)。 +**参考: バージョン URL は使えない。** Cloudflare には、`wrangler versions upload` で作る「バージョン URL」(`<ハッシュ>-kanban-web…`) という別の仕組みもあるが、Durable Objects を持つ Worker では作られない (実際に試して、開けないことを確認した)。 ## 3. 設定値とシークレット -シークレットは `wrangler secret put` で入れる (リポジトリには書かない)。プレビューには `--env preview` を付ける。 - -| 名前 | どこに | 内容 | -| ------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `BETTER_AUTH_SECRET` | web (本番、プレビュー) | 長いランダム文字列 (`openssl rand -hex 32`)。本番とプレビューで別の値にする | -| `BETTER_AUTH_URL` | web (`wrangler.jsonc` の `vars`) | そのアプリの公開 URL。ログインのオリジン確認に使うので、実際の URL と一致させる。本番は `https://kanban-web.ut-code.workers.dev`、プレビューは `env.preview.vars` に書いてある | -| `RESEND_API_KEY` | reminder-worker | Resend の API キー。詳しくは [ワーカー仕様](worker.md) | -| `REMINDER_FROM` | reminder-worker (`vars`) | 送信元アドレス。独自ドメインの検証が要る | -| `APP_URL` | reminder-worker (`vars`) | メールのリンクの先。本番の公開 URL (`https://kanban-web.ut-code.workers.dev`) | -| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | web | Google でログインするとき ([#2](https://github.com/ut-code/kanban/issues/2)) | +| 名前 | どこに | 内容 | +| ------------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| `BETTER_AUTH_SECRET` | web のシークレット (本番)、プレビューの base config のシークレット (全プレビュー共通) | 長いランダム文字列。本番とプレビューで別の値 | +| `BETTER_AUTH_URL` | web の `vars` (本番)。プレビューは `scripts/deploy-preview.sh` が渡す | 公開 URL。ログインのオリジン確認に使う | +| `RESEND_API_KEY` | reminder-worker のシークレット | Resend の API キー ([ワーカー仕様](worker.md)) | +| `REMINDER_FROM` | reminder-worker の `vars` | 送信元アドレス。独自ドメインの検証が要る | +| `APP_URL` | reminder-worker の `vars` | メールのリンクの先 (本番の公開 URL) | +| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | web | Google でログインするとき ([#2](https://github.com/ut-code/kanban/issues/2)) | ```sh cd apps/web -pnpm exec wrangler secret put BETTER_AUTH_SECRET -pnpm exec wrangler secret put BETTER_AUTH_SECRET --env preview -``` - -`BETTER_AUTH_URL` と `APP_URL` は、公開してよい値なので、シークレットではなく `wrangler.jsonc` の `vars` に書いてある。ローカルの `pnpm dev` は、`apps/web/.dev.vars` の値を使う。 - -シークレットを入れた直後は、反映まで数十秒かかる。そのあいだ、古いバージョンが応答して、ログインしているのに 401 になるなど、結果が一定しないことがある。少し待ってから試す。 - -**プレビューの URL の注意:** ログインは、`BETTER_AUTH_URL` と同じオリジンからのリクエストしか受け付けない。プレビューでログインを試すには、`https://kanban-web-preview.<アカウントのサブドメイン>.workers.dev` のように、**固定の URL** を `BETTER_AUTH_URL` に設定して、その URL で開く。ブランチごとに変わるバージョンの URL (`<ハッシュ>-kanban-web-preview…`) では、ログインできない。 - -## 4. 初回のデプロイ - -```sh -pnpm deploy:web # 本番の Web (中身は `pnpm --filter @todo/web run deploy`。`pnpm deploy` は pnpm 自身の別のコマンドなので、`run` が要る) -pnpm deploy:worker # リマインド用のワーカー -pnpm deploy:preview # プレビューの Web (Worker を作るため、最初に 1 回) +pnpm exec wrangler secret put BETTER_AUTH_SECRET # 本番 +pnpm exec wrangler preview base-config secret put BETTER_AUTH_SECRET # 全プレビュー共通 ``` -## 5. Cloudflare 側で、自動のデプロイとプレビューを設定する (Workers Builds) +シークレットを入れた直後は、反映まで数十秒かかる。そのあいだ、ログインしているのに 401 になるなど、結果が一定しないことがある。少し待ってから試す。 -ダッシュボードの Workers & Pages で、Worker ごとに、リポジトリ (`ut-code/kanban`) を接続する。Worker の名前は、設定ファイルの `name` と同じにする。 +## 4. Workers Builds の設定値 (ダッシュボード) -**`kanban-web`** (本番とプレビューを兼ねる): +**`kanban-web`:** -| 設定 | 値 | -| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Root directory | `/` | -| Build command | `pnpm install --frozen-lockfile && pnpm --filter @todo/web build` | -| Deploy command (`main`) | `pnpm --filter @todo/web exec wrangler d1 migrations apply kanban-db --remote && pnpm --filter @todo/web exec wrangler deploy` | -| Production branch | `main` | -| Non-production branch builds | 有効 | -| Non-production deploy command | `pnpm --filter @todo/web exec wrangler d1 migrations apply kanban-db-preview --remote --env preview && pnpm --filter @todo/web exec wrangler versions upload --env preview` | -| Build watch paths | `apps/web/**`、`packages/**`、`pnpm-lock.yaml` | +| 設定 | 値 | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| Root directory | `/` | +| Build command | `pnpm install --frozen-lockfile && pnpm --filter @todo/web build` | +| Deploy command (`main`) | `pnpm --filter @todo/web exec wrangler d1 migrations apply kanban-db --remote && pnpm --filter @todo/web exec wrangler deploy` | +| Production branch | `main` | +| Non-production branch builds | 有効 | +| Non-production deploy command | `./scripts/deploy-preview.sh` | +| Build watch paths | `apps/web/**`、`packages/**`、`scripts/**`、`pnpm-lock.yaml` | -- `main` に入ると、本番の DB にマイグレーションを適用してから、デプロイする。 -- `main` 以外のブランチや PR は、プレビュー用の DB にマイグレーションを適用して、`kanban-web-preview` の新しいバージョンとして、本番に出さずにアップロードする。ダッシュボードや PR のコメントに、そのバージョンの URL が出る (上の注意のとおり、ログインには固定の URL を使う)。 +`scripts/deploy-preview.sh` は、ビルドの環境変数 `WORKERS_CI_BRANCH` からブランチ名を読む。プレビュー用の D1 にマイグレーションを適用してから、`wrangler preview` でプレビューを作る。 -**`kanban-reminder-worker`** (本番だけ。プレビューなし): +**`kanban-reminder-worker`** (本番だけ): | 設定 | 値 | | --------------------- | ---------------------------------------------------------- | @@ -136,15 +106,30 @@ pnpm deploy:preview # プレビューの Web (Worker を作るため、最初 | Non-production builds | 無効 | | Build watch paths | `apps/reminder-worker/**`、`packages/**`、`pnpm-lock.yaml` | +## 5. 最初から作り直すとき (参考) + +```sh +cd apps/web +pnpm exec wrangler d1 create kanban-db +pnpm exec wrangler d1 create kanban-db-preview +pnpm exec wrangler r2 bucket create kanban-files +pnpm exec wrangler r2 bucket create kanban-files-preview +pnpm exec wrangler queues create kanban-reminders +pnpm exec wrangler queues create kanban-notifications +pnpm exec wrangler queues create kanban-mail-dead-letter +pnpm exec wrangler queues create kanban-notifications-preview +``` + +できた `database_id` を、`apps/web/wrangler.jsonc` (本番と `previews`)、`apps/web/wrangler.preview-migrations.jsonc`、`apps/reminder-worker/wrangler.jsonc` に書く。そのあと `pnpm db:migrate:remote`、`pnpm db:migrate:preview`、シークレット (3)、`pnpm deploy:web`、`pnpm deploy:worker`。 + ## 6. 確認すること - [ ] `main` へのマージで、本番にデプロイされ、マイグレーションが適用される -- [ ] PR のブランチで、プレビューのバージョンができ、本番のデータに触れない -- [ ] 本番で、ログイン、タスクの追加、添付、共有、リアルタイムの反映が動く (WebSocket と Durable Objects は、本番の設定で初めて確かめる) +- [ ] PR のブランチで、`<ブランチ名>-kanban-web.ut-code.workers.dev` にプレビューができ、ログインでき、本番のデータに触れない - [ ] リマインドの cron が動く (ダッシュボードの Triggers で、実行の履歴を見る) - [ ] メールが届く ([ワーカー仕様](worker.md) の「未了」) - [ ] デッドレターキュー (`kanban-mail-dead-letter`) に、送れなかったメールが溜まっていない ## 設定を変えたとき -`pnpm check:deploy` で、`apps/web` (本番とプレビュー) と `apps/reminder-worker` の設定が、まだ正しく読めることを確かめる (アップロードはしない)。CI の `check` ジョブでも実行する。 +`pnpm check:deploy` で、`apps/web` と `apps/reminder-worker` の設定が、まだ正しく読めることを確かめる (アップロードはしない)。CI の `check` ジョブでも実行する。 diff --git a/docs/development.md b/docs/development.md index a15507d..70e09bc 100644 --- a/docs/development.md +++ b/docs/development.md @@ -17,14 +17,14 @@ pnpm dev # http://localhost:8787 pnpm check # フォーマット、lint、型チェック、テストをまとめて実行 ``` -| コマンド | 内容 | -| ----------------------------------- | --------------------------------------------------------------------------------------------------------- | -| `pnpm format` / `pnpm format:check` | Oxfmt | -| `pnpm lint` | Oxlint | -| `pnpm typecheck` | TypeScript (`apps/web` はビルドも行う) | -| `pnpm test` | Vitest (`apps/web`、`apps/reminder-worker`、`packages/db`) | -| `pnpm test:e2e` | Playwright。本物のブラウザでアプリを動かす (下を参照) | -| `pnpm check:deploy` | Wrangler の設定 (本番、プレビュー、ワーカー) が読めることの確認 (アップロードなし。[デプロイ](deploy.md)) | +| コマンド | 内容 | +| ----------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `pnpm format` / `pnpm format:check` | Oxfmt | +| `pnpm lint` | Oxlint | +| `pnpm typecheck` | TypeScript (`apps/web` はビルドも行う) | +| `pnpm test` | Vitest (`apps/web`、`apps/reminder-worker`、`packages/db`) | +| `pnpm test:e2e` | Playwright。本物のブラウザでアプリを動かす (下を参照) | +| `pnpm check:deploy` | Wrangler の設定 (Web とメールの Worker) が読めることの確認 (アップロードなし。[デプロイ](deploy.md)) | CI (`.github/workflows/ci.yml`) は、PR と main への push で、`check` (`pnpm check` と `pnpm check:deploy`) と `e2e` (`pnpm test:e2e`) の 2 つのジョブを実行する。E2E は遅く不安定になりやすいので、速いチェックの結果を隠さないよう、別のジョブにしている。 diff --git a/package.json b/package.json index 3ec5e57..b6166d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "kanban", "private": true, + "type": "module", "scripts": { "dev": "pnpm --filter @todo/web dev", "build": "pnpm -r build", @@ -14,11 +15,11 @@ "db:migrate:local": "pnpm --filter @todo/web exec wrangler d1 migrations apply kanban-db --local --persist-to .wrangler/state-drizzle-baseline", "db:migrate:remote": "pnpm --filter @todo/web exec wrangler d1 migrations apply kanban-db --remote", "test:e2e": "playwright test", - "db:migrate:preview": "pnpm --filter @todo/web exec wrangler d1 migrations apply kanban-db-preview --remote --env preview", + "db:migrate:preview": "pnpm --filter @todo/web exec wrangler d1 migrations apply kanban-db-preview --remote --config wrangler.preview-migrations.jsonc", "deploy:web": "pnpm --filter @todo/web run deploy", - "deploy:preview": "pnpm --filter @todo/web build && pnpm --filter @todo/web exec wrangler deploy --env preview", + "deploy:preview": "./scripts/deploy-preview.sh", "deploy:worker": "pnpm --filter @todo/reminder-worker run deploy", - "check:deploy": "pnpm --filter @todo/web build && pnpm --filter @todo/web exec wrangler deploy --dry-run --outdir .wrangler/dry-run && pnpm --filter @todo/web exec wrangler deploy --dry-run --env preview --outdir .wrangler/dry-run-preview && pnpm --filter @todo/reminder-worker exec wrangler deploy --dry-run --outdir .wrangler/dry-run" + "check:deploy": "pnpm --filter @todo/web build && pnpm --filter @todo/web exec wrangler deploy --dry-run --outdir .wrangler/dry-run && pnpm --filter @todo/reminder-worker exec wrangler deploy --dry-run --outdir .wrangler/dry-run" }, "devDependencies": { "@playwright/test": "catalog:", diff --git a/scripts/deploy-preview.sh b/scripts/deploy-preview.sh new file mode 100755 index 0000000..461b2ee --- /dev/null +++ b/scripts/deploy-preview.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Deploys the current branch as a Worker Preview of kanban-web. Workers Builds runs this for every +# branch other than main ("Non-production branch deploy command"); it also works by hand. +set -euo pipefail + +cd "$(dirname "$0")/.." + +# Workers Builds tells the branch in WORKERS_CI_BRANCH; by hand, take the checked-out branch. +BRANCH="${WORKERS_CI_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}" +NAME="$(node scripts/preview-name.ts "$BRANCH")" +URL="https://${NAME}-kanban-web.ut-code.workers.dev" + +echo "Preview of ${BRANCH} → ${URL}" + +pnpm --filter @todo/web build +# All Previews share one preview database, so this is the same migration step for every branch. +pnpm --filter @todo/web exec wrangler d1 migrations apply kanban-db-preview --remote \ + --config wrangler.preview-migrations.jsonc +# Login only accepts requests from BETTER_AUTH_URL, so give this Preview its own address. +pnpm --filter @todo/web exec wrangler preview --name "$NAME" --var "BETTER_AUTH_URL:${URL}" diff --git a/scripts/preview-name.ts b/scripts/preview-name.ts new file mode 100644 index 0000000..3d08219 --- /dev/null +++ b/scripts/preview-name.ts @@ -0,0 +1,9 @@ +// Prints the Preview name for a branch: `node scripts/preview-name.ts `. +import { previewName } from '../apps/web/src/lib/preview-name.ts' + +const branch = process.argv[2] +if (!branch) { + console.error('usage: node scripts/preview-name.ts ') + process.exit(1) +} +console.log(previewName(branch)) From de4ef18de4b3ccef14d785b5ddd34c6e7ddd6b11 Mon Sep 17 00:00:00 2001 From: tknkaa Date: Fri, 2 Oct 2026 21:41:36 +0900 Subject: [PATCH 02/10] chore: pin Node 24 for builds, which the preview script needs to run TypeScript directly --- .node-version | 1 + docs/deploy.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .node-version diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24 diff --git a/docs/deploy.md b/docs/deploy.md index 913fa54..9657c34 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -94,7 +94,7 @@ pnpm exec wrangler preview base-config secret put BETTER_AUTH_SECRET # 全 | Non-production deploy command | `./scripts/deploy-preview.sh` | | Build watch paths | `apps/web/**`、`packages/**`、`scripts/**`、`pnpm-lock.yaml` | -`scripts/deploy-preview.sh` は、ビルドの環境変数 `WORKERS_CI_BRANCH` からブランチ名を読む。プレビュー用の D1 にマイグレーションを適用してから、`wrangler preview` でプレビューを作る。 +`scripts/deploy-preview.sh` は、ビルドの環境変数 `WORKERS_CI_BRANCH` からブランチ名を読む。Node で `.ts` を直接動かすので、Node 24 を使う (`.node-version`。Workers Builds も CI もこれに従う)。プレビュー用の D1 にマイグレーションを適用してから、`wrangler preview` でプレビューを作る。 **`kanban-reminder-worker`** (本番だけ): From 6a8ad3bbc635f6393ade91bd364836880487b3db Mon Sep 17 00:00:00 2001 From: tknkaa Date: Fri, 2 Oct 2026 21:53:40 +0900 Subject: [PATCH 03/10] chore: rebuild with the updated Workers Builds commands From fb719e57ce78cc09c3414a04a8b04b0ec398ec7b Mon Sep 17 00:00:00 2001 From: tknkaa Date: Fri, 2 Oct 2026 22:05:19 +0900 Subject: [PATCH 04/10] chore: rebuild with the corrected Workers Builds settings From edd4e052f19185c746a4ef09efee8e5ef872bc9a Mon Sep 17 00:00:00 2001 From: tknkaa Date: Fri, 2 Oct 2026 22:10:36 +0900 Subject: [PATCH 05/10] chore: rebuild as a Worker Preview From 1ed743b5a36c3b789e25cc44928c23da8b45ed06 Mon Sep 17 00:00:00 2001 From: tknkaa Date: Fri, 2 Oct 2026 22:14:20 +0900 Subject: [PATCH 06/10] docs: record the Workers Builds settings and how to check them with cf --- docs/deploy.md | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/docs/deploy.md b/docs/deploy.md index 9657c34..6f422be 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -98,13 +98,32 @@ pnpm exec wrangler preview base-config secret put BETTER_AUTH_SECRET # 全 **`kanban-reminder-worker`** (本番だけ): -| 設定 | 値 | -| --------------------- | ---------------------------------------------------------- | -| Root directory | `/` | -| Build command | `pnpm install --frozen-lockfile` | -| Deploy command | `pnpm --filter @todo/reminder-worker exec wrangler deploy` | -| Non-production builds | 無効 | -| Build watch paths | `apps/reminder-worker/**`、`packages/**`、`pnpm-lock.yaml` | +| 設定 | 値 | +| ----------------- | ---------------------------------------------------------------------------------------- | +| Root directory | `/` | +| Build command | `pnpm --filter @todo/reminder-worker typecheck` (ビルドの手順はないので、型チェックだけ) | +| Deploy command | `pnpm --filter @todo/reminder-worker exec wrangler deploy` | +| Production branch | `main` | +| Previews | 無効 | + +**注意 (はまったところ):** + +- 本番のブランチは `main` にする。最初の設定で、本番のブランチが PR のブランチになっていて、PR のプッシュが本番のビルドとして扱われていた。 +- `main` 以外のブランチは、「Previews」を有効にして、Preview command に `./scripts/deploy-preview.sh` を入れる。Previews が無効だと、古い方式の「非本番のデプロイ」になり、PR のコメントにプレビューの URL が出ない。 +- ブランチの最初のビルドのときに、そのときの設定が写される。設定は、PR を出す前に済ませる。 + +### 設定を確かめる (`cf`) + +ダッシュボードの表示と、実際に保存された値が違うことがあった。Cloudflare の CLI (`cf`) で、保存された値を直接読める。 + +```sh +npx -y cf auth login # 初回だけ。ブラウザで承認する +export CLOUDFLARE_ACCOUNT_ID=df6c3acd32f66bd1eb95e50607684297 +npx -y cf workers scripts search # Worker のタグ (内部 ID) を調べる +npx -y cf builds workers get <タグ> # 本番のブランチ、コマンド、Previews の有無 +npx -y cf builds triggers list --external-script-id <タグ> +npx -y cf builds logs get <ビルドの UUID> # ビルドのログ +``` ## 5. 最初から作り直すとき (参考) From f9eae27dc908c8c3b582a59eac76c33c96b902ee Mon Sep 17 00:00:00 2001 From: tknkaa Date: Fri, 2 Oct 2026 22:17:38 +0900 Subject: [PATCH 07/10] chore: rebuild with the kanban build token From a54c18c520fbce4e761bb3a032710646cb34afe7 Mon Sep 17 00:00:00 2001 From: tknkaa Date: Fri, 2 Oct 2026 22:20:06 +0900 Subject: [PATCH 08/10] chore: rebuild the preview with the kanban build token From e8abbcad371b2dc93171a36f4df5f1aa32fe2313 Mon Sep 17 00:00:00 2001 From: tknkaa Date: Fri, 2 Oct 2026 22:21:52 +0900 Subject: [PATCH 09/10] docs: record the kanban build tokens and how per-branch preview triggers keep their settings --- docs/deploy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploy.md b/docs/deploy.md index 6f422be..5cdc0cd 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -110,7 +110,7 @@ pnpm exec wrangler preview base-config secret put BETTER_AUTH_SECRET # 全 - 本番のブランチは `main` にする。最初の設定で、本番のブランチが PR のブランチになっていて、PR のプッシュが本番のビルドとして扱われていた。 - `main` 以外のブランチは、「Previews」を有効にして、Preview command に `./scripts/deploy-preview.sh` を入れる。Previews が無効だと、古い方式の「非本番のデプロイ」になり、PR のコメントにプレビューの URL が出ない。 -- ブランチの最初のビルドのときに、そのときの設定が写される。設定は、PR を出す前に済ませる。 +- ブランチの最初のビルドのときに、そのときの設定 (コマンド、トークン) が、そのブランチのプレビュー専用のトリガーに写される。あとから Worker の設定を変えても、既存のブランチには効かない。既存のブランチも直すときは、`cf builds triggers list --external-script-id <プレビューの ID>` でトリガーを見つけて、`cf builds triggers update` で直す (プレビューの ID は `cf workers-builds workers previews list --script-tag ` で分かる)。 ### 設定を確かめる (`cf`) From 10ceaea0f101b3bab1a733726aa50d06c1bda0cf Mon Sep 17 00:00:00 2001 From: tknkaa Date: Fri, 2 Oct 2026 22:23:47 +0900 Subject: [PATCH 10/10] docs: list the kanban build tokens --- docs/deploy.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/deploy.md b/docs/deploy.md index 5cdc0cd..1c8be6c 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -6,16 +6,17 @@ Cloudflare (ut-code のアカウント `ut.code();`) へのデプロイとプレ ## いまの状態 -| | 状態 | -| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| 本番の Web (`kanban-web`) | デプロイ済み: https://kanban-web.ut-code.workers.dev | -| メールを送る Worker (`kanban-reminder-worker`) | デプロイ済み (cron は 15 分ごと)。公開 URL はない (`workers_dev: false`) | -| プレビュー | Worker Previews。ブランチごとに `https://<ブランチ名>-kanban-web.ut-code.workers.dev` | -| リソース | D1 2 個、R2 2 個、キュー 4 つ (下の表)。マイグレーションは本番とプレビューの両方に適用済み | -| シークレット | `BETTER_AUTH_SECRET` を、本番とプレビュー (全プレビュー共通) に設定済み (別の値) | -| Workers Builds | ダッシュボードで設定済み (下の 4 の値) | -| メール (Resend) | **未設定** ([#8](https://github.com/ut-code/kanban/issues/8))。それまで、本番のメールの送信は失敗して、デッドレターキューに溜まる | -| Google ログイン | **未設定** ([#2](https://github.com/ut-code/kanban/issues/2)) | +| | 状態 | +| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| 本番の Web (`kanban-web`) | デプロイ済み: https://kanban-web.ut-code.workers.dev | +| メールを送る Worker (`kanban-reminder-worker`) | デプロイ済み (cron は 15 分ごと)。公開 URL はない (`workers_dev: false`) | +| プレビュー | Worker Previews。ブランチごとに `https://<ブランチ名>-kanban-web.ut-code.workers.dev` | +| リソース | D1 2 個、R2 2 個、キュー 4 つ (下の表)。マイグレーションは本番とプレビューの両方に適用済み | +| シークレット | `BETTER_AUTH_SECRET` を、本番とプレビュー (全プレビュー共通) に設定済み (別の値) | +| Workers Builds | ダッシュボードで設定済み (下の 4 の値) | +| ビルドのトークン | `kanban-deploy` (`kanban-web` の本番とプレビュー)、`kanban-reminder-deploy` (`kanban-reminder-worker`)。ほかのプロジェクトのトークンは使わない | +| メール (Resend) | **未設定** ([#8](https://github.com/ut-code/kanban/issues/8))。それまで、本番のメールの送信は失敗して、デッドレターキューに溜まる | +| Google ログイン | **未設定** ([#2](https://github.com/ut-code/kanban/issues/2)) | ## 名前