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
67 changes: 67 additions & 0 deletions .github/actions/verify/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Verify worker
description: >-
lockfile どおりに依存を入れて lint / typecheck / test を回し、Worker が実際に
バンドルできることを dev・production 双方の設定で確認する。検証用ワークフローと
デプロイ用ワークフローで同じ手順を踏むため composite action に切り出してある。

inputs:
node-version:
description: Node.js のバージョン (package.json の engines と揃えること)
required: false
default: "22"

# ローカル action は checkout 済みでないと解決できないため、checkout は
# 呼び出し側のワークフローに置いてある。
runs:
using: composite
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: npm

# npm install と違い package-lock.json を書き換えないので、ローカルで
# 動かしたのと同じ wrangler / biome / TypeScript の版で検証できる。
# wrangler もこの lockfile から入るため、デプロイに使う版の固定先は
# ワークフロー側ではなく package-lock.json 一箇所で済む。
- name: Install dependencies
shell: bash
run: npm ci

- name: Lint
shell: bash
run: npm run lint

- name: Typecheck
shell: bash
run: npm run typecheck

- name: Test
shell: bash
run: npm test

# tsc は型しか見ないので、import の解決ミスや nodejs_compat で賄えない
# Node API はバンドルして初めて落ちる。--dry-run は Cloudflare API を
# 叩かないため認証情報なしで回せる。
#
# dev と production を両方バンドルするのは、wrangler.jsonc の env.production
# 側だけが壊れている状態を master へ入れる前に捕まえるため。dev への push
# では production 設定に一切触れないまま緑になってしまう。
- name: Build (dry-run)
shell: bash
env:
WRANGLER_SEND_METRICS: "false"
# wrangler の色付けが Total Upload 行に混ざると要約が読めなくなる
NO_COLOR: "1"
run: |
# dev は wrangler.jsonc の top-level 設定。wrangler 4 は環境が複数ある
# 状態で --env を省くと警告を出すため、空文字でも明示する。
for target in "" production; do
label="${target:-dev}"
npx wrangler deploy --env="$target" --dry-run \
--outdir "$RUNNER_TEMP/bundle-$label" 2>&1 | tee "$RUNNER_TEMP/$label.log"
# Workers の上限は gzip 後で 10 MiB。今は 1/10 にも届かないので
# 失敗にはせず、増え方が見えるよう要約に残すだけにする。
size=$(grep -m1 'Total Upload' "$RUNNER_TEMP/$label.log" || true)
echo "- \`$label\`: ${size:-size unknown}" >> "$GITHUB_STEP_SUMMARY"
done
85 changes: 85 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# lint / typecheck / test が通ることと、Worker がバンドルできることを検証する
# だけのワークフロー。デプロイはしない。
#
# デプロイは環境ごとに別ファイルへ分けてある:
# dev -> deploy_dev.yml (trainlcd-worker-dev)
# master -> deploy_production.yml (trainlcd-worker)
# デプロイ先をトリガとファイルで固定することで、他のブランチが誤って
# どこかの環境へ向くことがないようにしている。
on:
pull_request:
# GitHub Actions は YAML のアンカー / エイリアスを解釈しないため、
# このリストは push 側とも、デプロイ用の 2 ファイルとも二重に書く必要が
# ある。片方だけ直さないこと。
paths:
- "src/**"
- "test/**"
- "scripts/**"
- "package.json"
- "package-lock.json"
- "tsconfig.json"
- "biome.json"
- "jest.config.js"
- "wrangler.jsonc"
- ".github/actions/verify/action.yml"
- ".github/workflows/ci.yml"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
# deploy 用の 2 ファイルはどちらも pull_request で起動しないため、
# ここに載せておかないと変更した PR がどの workflow も通らないまま
# マージされ、デプロイ時に初めて動くことになる。
- ".github/workflows/deploy_dev.yml"
- ".github/workflows/deploy_production.yml"
push:
# dev / master は deploy_dev.yml / deploy_production.yml が同じ composite
# action で検証してからデプロイするため、ここでは走らせない。
branches-ignore:
- dev
- master
paths:
- "src/**"
- "test/**"
- "scripts/**"
- "package.json"
- "package-lock.json"
- "tsconfig.json"
- "biome.json"
- "jest.config.js"
- "wrangler.jsonc"
- ".github/actions/verify/action.yml"
- ".github/workflows/ci.yml"
# deploy 用の 2 ファイルはどちらも pull_request で起動しないため、
# ここに載せておかないと変更した PR がどの workflow も通らないまま
# マージされ、デプロイ時に初めて動くことになる。
- ".github/workflows/deploy_dev.yml"
- ".github/workflows/deploy_production.yml"
workflow_dispatch:

name: Continuous integration

# 同じ PR / ブランチに続けて push したとき、古い方は結果が要らない。
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
verify:
name: Lint, typecheck, test and build
runs-on: ubuntu-latest

# ここでは environment を宣言しない。environment は if: と違って
# ジョブが走れば必ず適用されるため、宣言すると全ブランチ・全 PR が
# その環境へのデプロイとして履歴に載り、環境 Secret (デプロイ用の
# CLOUDFLARE_API_TOKEN を含む) が任意のブランチのビルドから触れる。
#
# 検証は --dry-run で Cloudflare API を叩かないため、そもそも認証情報が要らない。
steps:
# checkout は既定で GITHUB_TOKEN を .git/config に残す。後続の npm ci は
# 依存パッケージの install スクリプトを実行するため読み取られうる。
# ここから先で git 認証は使わない。
- uses: actions/checkout@v4
with:
persist-credentials: false

- uses: ./.github/actions/verify
69 changes: 69 additions & 0 deletions .github/workflows/deploy_dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# dev を dev 環境 (trainlcd-worker-dev) へデプロイする。
#
# デプロイ先はこのファイルとトリガで固定してある。ブランチを式で判定して
# 環境を選ぶ作りにすると、environment は if: と違ってジョブが走れば必ず
# 適用されるため、意図しないブランチがこの環境の履歴と Secret に触れる。
on:
push:
branches:
- dev
# GitHub Actions は YAML のアンカー / エイリアスを解釈しないため、
# このリストは ci.yml / deploy_production.yml とも二重に書く必要がある。
# 片方だけ直さないこと。
paths:
- "src/**"
- "test/**"
- "scripts/**"
- "package.json"
- "package-lock.json"
- "tsconfig.json"
- "biome.json"
- "jest.config.js"
- "wrangler.jsonc"
- ".github/actions/verify/action.yml"
- ".github/workflows/deploy_dev.yml"
workflow_dispatch:

name: Deploy to dev

# 同時に流れると、先に始まった古い版が後から上書きしうる。
concurrency:
group: deploy-dev
cancel-in-progress: false

permissions:
contents: read

jobs:
deploy:
name: Verify and deploy to dev
runs-on: ubuntu-latest

# workflow_dispatch にはブランチ絞り込みが無いので、ここで塞ぐ。
# push は on: branches で dev に限定済み。
if: github.ref == 'refs/heads/dev'

environment: dev

steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- uses: ./.github/actions/verify

# 直前の検証で --dry-run 済みのものと同じ入力から同じバンドルが組み上がる。
# wrangler は node_modules から解決されるので、検証と同じ版が走る。
#
# dev は wrangler.jsonc の top-level 設定なので環境名は空にする。
# wrangler 4 は環境が複数あると --env の省略を警告するため、空でも明示する。
#
# Worker の secrets (SESSION_JWT_SECRET など) はここでは触らない。
# deploy は既存の secrets を保持するため、投入は scripts/put-secrets.sh で
# 手元から行う運用のままでよい。
- name: Deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
WRANGLER_SEND_METRICS: "false"
run: npx wrangler deploy --env=""
68 changes: 68 additions & 0 deletions .github/workflows/deploy_production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# master を production (trainlcd-worker) へデプロイする。
#
# デプロイ先はこのファイルとトリガで固定してある。ブランチを式で判定して
# 環境を選ぶ作りにすると、environment は if: と違ってジョブが走れば必ず
# 適用されるため、意図しないブランチがこの環境の履歴と Secret に触れる。
on:
push:
branches:
- master
# GitHub Actions は YAML のアンカー / エイリアスを解釈しないため、
# このリストは ci.yml / deploy_dev.yml とも二重に書く必要がある。
# 片方だけ直さないこと。
paths:
- "src/**"
- "test/**"
- "scripts/**"
- "package.json"
- "package-lock.json"
- "tsconfig.json"
- "biome.json"
- "jest.config.js"
- "wrangler.jsonc"
- ".github/actions/verify/action.yml"
- ".github/workflows/deploy_production.yml"
workflow_dispatch:

name: Deploy to production

# 同時に流れると、先に始まった古い版が後から上書きしうる。
concurrency:
group: deploy-production
cancel-in-progress: false

permissions:
contents: read

jobs:
deploy:
name: Verify and deploy to production
runs-on: ubuntu-latest

# workflow_dispatch にはブランチ絞り込みが無いので、ここで塞ぐ。
# push は on: branches で master に限定済み。
if: github.ref == 'refs/heads/master'

environment: production

steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- uses: ./.github/actions/verify

# 直前の検証で --dry-run 済みのものと同じ入力から同じバンドルが組み上がる。
# wrangler は node_modules から解決されるので、検証と同じ版が走る。
#
# production は wrangler.jsonc の env.production を指す。
#
# Worker の secrets (SESSION_JWT_SECRET など) はここでは触らない。
# deploy は既存の secrets を保持するため、投入は scripts/put-secrets.sh で
# 手元から行う運用のままでよい。
- name: Deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
WRANGLER_SEND_METRICS: "false"
run: npx wrangler deploy --env production
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,45 @@ npm run deploy:prod # wrangler deploy --env production
npm run tail # follow logs
```

### CI / CD (GitHub Actions)

Deploys run from GitHub Actions. The target is fixed by the workflow file and
its trigger rather than chosen by an expression, so no branch can point at an
environment it was not meant to reach:

| Workflow | Trigger | Result |
| ------------------------------------ | -------------------------------- | ----------------------------------- |
| `.github/workflows/ci.yml` | PRs, and pushes to other branches | Verify only, no deploy |
| `.github/workflows/deploy_dev.yml` | push to `dev` | Deploy `trainlcd-worker-dev` |
| `.github/workflows/deploy_production.yml` | push to `master` | Deploy `trainlcd-worker` |

All three run the same `.github/actions/verify` composite action first — `npm
ci`, lint, typecheck, tests, and a `wrangler deploy --dry-run` of **both** the
dev and the production config. The dry run bundles the Worker for real, so
import mistakes and missing `nodejs_compat` APIs fail there rather than at
deploy time, and building the production config on every run catches an
`env.production` that only breaks after the merge to `master`. `npm ci` installs
wrangler from `package-lock.json`, so the version that deploys is the version
the lockfile pins — there is no second place to bump.

Each deploy workflow needs two secrets on its GitHub environment (`dev` and
`production` respectively):

- `CLOUDFLARE_API_TOKEN` — the *Edit Cloudflare Workers* template plus
**Queues: Edit**, since `wrangler deploy` also applies the queue consumer
settings from `wrangler.jsonc`.
- `CLOUDFLARE_ACCOUNT_ID` — `wrangler.jsonc` carries no `account_id`.

Keeping them on the environment rather than on the repository is what stops an
arbitrary branch from reading the production token: `ci.yml` deliberately
declares no `environment`, and it needs no credentials because `--dry-run` never
calls the Cloudflare API.

Worker secrets (`SESSION_JWT_SECRET`, `OCTOKIT_PAT`, …) are **not** touched by
the workflows. `wrangler deploy` preserves the secrets already on a Worker, so
they stay a manual `scripts/put-secrets.sh` step — see [Setting
secrets](#setting-secrets).

## Client wire protocol

`POST /tts` and `POST /postFeedback` keep the Firebase callable-compatible wire
Expand Down
Loading