Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 37 additions & 0 deletions apps/web/src/lib/preview-name.test.ts
Original file line number Diff line number Diff line change
@@ -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')
})
})
30 changes: 30 additions & 0 deletions apps/web/src/lib/preview-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createHash } from 'node:crypto'

const WORKER = 'kanban-web'
// A preview's address is `<name>-kanban-web.<subdomain>.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`
}
60 changes: 28 additions & 32 deletions apps/web/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -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
// `<branch>-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",
},
],
},
},
},
}
14 changes: 14 additions & 0 deletions apps/web/wrangler.preview-migrations.jsonc
Original file line number Diff line number Diff line change
@@ -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",
},
],
}
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`) などは描いていない。

Expand Down
Loading
Loading