diff --git a/.claude/skills/backend-ci-trigger/SKILL.md b/.claude/skills/backend-ci-trigger/SKILL.md new file mode 100644 index 0000000..2afd24e --- /dev/null +++ b/.claude/skills/backend-ci-trigger/SKILL.md @@ -0,0 +1,133 @@ +--- +name: backend-ci-trigger +description: >- + featureブランチでバックエンドの変更をpushしたあと、対応するCIが自動実行 + されないため手動でCIを発火・確認する際に使う。「CIを回して」「CIの結果を + 確認して」等の指示で使う。 +allowed-tools: Bash(gh:*) +--- + +# backend-ci-trigger + +各バックエンドCI(`.github/workflows/*-ci.yml`)は次の2点により、featureブランチへの +pushでは**自動実行されない**。 + +1. `paths` トリガーが対応するバックエンドディレクトリのみに限定されている +2. `branches-ignore: ['feature/issue-*']` により、`feature/issue-*` へのpush自体が + トリガー対象外 + +そのため `feature/issue-N` ブランチ上でCI結果を確認したい場合は、`workflow_dispatch` +で手動発火する。`feature/issue-*` **以外**のブランチ(develop/main に限らない)への +pushでは、該当pathsに応じて自動実行される。 + +`workflow_dispatch` は、対象のワークフローファイルがdefaultブランチ(`main`)に +既に存在している場合のみ使える。new-backend-scaffold Skillで新規追加したワークフロー +ファイルは、`main` へマージされるまで `gh workflow run` で手動発火できない +(GitHubがdispatch可否をdefaultブランチ上の定義で判定するため)。この場合は、 +develop→main同期後に確認するか、ワークフローファイルのみ先にdevelopおよびmainへ +反映しておく。 + +## ワークフロー対応表 + +| ワークフローファイル | 対象パス | +|---|---| +| `go-gin-ci.yml` | `backends/go-gin/**` | +| `java-springboot-ci.yml` | `backends/java-springboot/**` | +| `php-cakephp-ci.yml` | `backends/php-cakephp/**` | +| `php-codeigniter-ci.yml` | `backends/php-codeigniter/**` | +| `php-fuelphp-ci.yml` | `backends/php-fuelphp/**` | +| `python-django-ci.yml` | `backends/python-django/**` | +| `ruby-rails-ci.yml` | `backends/ruby-rails/**` | + +`gh workflow run` にはワークフロー表示名(日本語・括弧付き)ではなく、上記の +ファイル名を使う(曖昧さがなく確実)。 + +## 発火対象の判断 + +変更したファイルパスから、対応するバックエンドのワークフローを特定する。 +複数バックエンドにまたがる変更(例: 共通ドキュメントやCI設定自体の変更)は、 +影響するワークフローをそれぞれ個別に発火する(一括発火するコマンドは無い)。 + +```bash +BASE=develop # git-flowでこのfeatureブランチの派生元とした基準ブランチに合わせる +git diff --name-only "$BASE"... +``` + +- `backends//**` に一致する変更 → 出力パスの `` 部分を上表と + 突き合わせ、対応するワークフローのみ発火する。 +- `.github/workflows/**`、`docker/**`、リポジトリ直下の設定ファイル等、 + 特定のバックエンドに閉じない変更(=上記以外の変更)が1件でも含まれる場合は、 + 影響範囲を個別に切り分けず、上表の7ワークフロー全てを発火するフォールバックを + 取る。 + +## 手動発火と結果確認 + +`gh workflow run` は対象runのURLを返せる場合は返す(`gh`のバージョンやイベント +種別により返らないこともある)。まずそのURLからrun IDを取得することを優先する。 +取れなかった場合は `gh run list` へフォールバックするが、`createdAt` は秒精度 +しかなく発火と同じ秒に既存runが作られていると誤って一致しうるため、時刻ではなく +**発火前に存在したrunのdatabaseId集合を記録し、そこに含まれない新しいID**を +候補とする。候補が複数見つかった場合は一意に特定できないため中断する: + +```bash +set -euo pipefail +WORKFLOW=go-gin-ci.yml # 対象ワークフローファイル名(上表を参照して置き換える) +N=34 # このfeatureブランチに対応するIssue番号に置き換える + +EXISTING_IDS=$(gh run list --repo bobtabo/authorization-showcase \ + --workflow="$WORKFLOW" --branch="feature/issue-$N" --event=workflow_dispatch \ + --limit 100 --json databaseId --jq '.[].databaseId' | sort -u) + +DISPATCH_OUTPUT=$(gh workflow run "$WORKFLOW" --repo bobtabo/authorization-showcase \ + --ref "feature/issue-$N" 2>&1) +echo "$DISPATCH_OUTPUT" + +RUN_ID=$(echo "$DISPATCH_OUTPUT" | grep -oE '/actions/runs/[0-9]+' | grep -oE '[0-9]+' | head -1 || true) + +if [ -z "$RUN_ID" ]; then + # URLが返らない場合のフォールバック。発火前に存在しなかったIDだけを候補とする + for i in $(seq 1 20); do + CURRENT_IDS=$(gh run list --repo bobtabo/authorization-showcase \ + --workflow="$WORKFLOW" --branch="feature/issue-$N" --event=workflow_dispatch \ + --limit 100 --json databaseId --jq '.[].databaseId' | sort -u) + NEW_IDS=$(comm -13 <(echo "$EXISTING_IDS") <(echo "$CURRENT_IDS")) + COUNT=$(echo "$NEW_IDS" | grep -c . || true) + if [ "$COUNT" -eq 1 ]; then + RUN_ID="$NEW_IDS" + break + elif [ "$COUNT" -gt 1 ]; then + echo "runの候補が複数あり一意に特定できません。手動でrun IDを確認してください" >&2 + exit 1 + fi + sleep 3 + done +fi +if [ -z "$RUN_ID" ]; then + echo "新しいrunを検出できませんでした" >&2 + exit 1 +fi + +# 完了まで待つ。--exit-status はCI失敗時に非0で終了するため、set -e 下でも +# 後続のログ取得が実行されるようステータスを一旦保存する +set +e +gh run watch --repo bobtabo/authorization-showcase "$RUN_ID" --exit-status +WATCH_STATUS=$? +set -e + +# ログ取得自体の失敗(gh CLIの既知の不具合等)でこのステータスが失われないよう、 +# 失敗しても警告のみ出して握りつぶす +if ! gh run view --repo bobtabo/authorization-showcase "$RUN_ID" --log; then + echo "警告: ログの取得に失敗しました(run自体の結果には影響しません)" >&2 +fi +exit "$WATCH_STATUS" +``` + +## 注意 + +- 全バックエンドCIは同一のngrokトンネル/LocalStackを共有しており、 + `concurrency: group: shared-authserver-tunnel, cancel-in-progress: false` + で直列実行される。複数ワークフローを続けて発火すると、後発のものは前段の + 完了待ちで少し時間がかかる。 +- develop/main へのマージ前にfeatureブランチ側でCIグリーンを確認したい場合は、 + このSkillで対象ワークフローを手動発火してから確認する(git-flow SkillでPRを + 作成しても、featureブランチ自体のpushではCIは動かない点に注意)。 diff --git a/.claude/skills/backend-dispatch/SKILL.md b/.claude/skills/backend-dispatch/SKILL.md new file mode 100644 index 0000000..c695634 --- /dev/null +++ b/.claude/skills/backend-dispatch/SKILL.md @@ -0,0 +1,61 @@ +--- +name: backend-dispatch +description: >- + 「Goのバックエンドで◯◯を直して」のように言語名だけで指示された際に、正しい + backends/ 配下のディレクトリと docker/bin/docker-*.sh のラッパースクリプトに + 振り分ける際に使う。曖昧なPHP指示(CakePHP/CodeIgniter/FuelPHPのどれか不明)の + ときにも使う。 +allowed-tools: Bash(docker:*), Bash(git:*) +--- + +# backend-dispatch + +このリポジトリは同一の認可サーバーAPI(JWT発行/検証)を7言語・FWで実装するショーケース。 +どのバックエンドを指しているかを機械的に確定させ、実行は必ずDockerラッパースクリプト +経由で行う。 + +## 対応表 + +| 呼ばれ方の例 | ソースディレクトリ | Docker操作 | execで入るシェル | +|---|---|---|---| +| Go, Gin | `backends/go-gin/` | `docker/bin/docker-go.sh` | `sh` | +| Java, Spring Boot | `backends/java-springboot/` | `docker/bin/docker-java.sh` | `bash` | +| CakePHP, Cake | `backends/php-cakephp/` | `docker/bin/docker-php-cake.sh` | `bash` | +| CodeIgniter, CI | `backends/php-codeigniter/` | `docker/bin/docker-php-codeigniter.sh` | `bash` | +| FuelPHP, Fuel | `backends/php-fuelphp/` | `docker/bin/docker-php-fuel.sh` | `bash` | +| Python, Django | `backends/python-django/` | `docker/bin/docker-python.sh` | `bash` | +| Ruby, Rails | `backends/ruby-rails/` | `docker/bin/docker-ruby.sh` | `bash` | + +- 「PHPのバックエンドで」のように3フレームワークのどれか特定できない指示を受けた場合は、 + 推測で決めずに CakePHP / CodeIgniter / FuelPHP のどれかをユーザーに確認する。 +- 「全バックエンドで」「7言語すべてで」という指示は、上記7ディレクトリ全てに同じ変更を + 展開する。一括Docker操作は `docker/bin/docker-backends.sh {up|down}` を使う + (docker-ops Skill参照)。 + +## 実行方針 + +- **ソース編集**: `backends//` はDockerコンテナへbind mountされているため、 + ホスト側で直接編集してよい(Edit/Writeツールで通常通り編集する)。 +- **テスト・ビルド・CLI実行**: 必ずコンテナ内で行う。ホストに言語ランタイムが + 入っていても、ホストで直接 `go test` 等を実行しない。 + ```bash + X=go # 対応表の値に置き換える: go/java/php-cake/php-codeigniter/php-fuel/python/ruby + "docker/bin/docker-$X.sh" exec + # コンテナ内シェルに入った状態で、通常のテスト/ビルドコマンドを実行する + ``` + 非対話的に1コマンドだけ実行したい場合は、対応する `docker/local/app-/docker-compose.yml` + を直接指定する(`docker exec <コンテナ名>` のようにコンテナ名を直書きしない。 + プロジェクト名・サービス名がバックエンドごとに異なるため取り違えやすい)。 + `CMD` は文字列ではなく配列にし、`"${CMD[@]}"` で展開する(文字列のまま + `$CMD` と書くと、引数中のクォートや `*` がホスト側のシェルで単語分割・ + パス展開されてしまい、意図しない引数がコンテナに渡る): + ```bash + X=go # 対応表の値に置き換える + SERVICE=go # execで指定するサービス名: go=go, java=java, php系=php, python=python, ruby=rb-rails + CMD=(go test ./...) # 実行したいコマンド(配列で書く) + + docker compose -p "showcase-$X" -f "docker/local/app-$X/docker-compose.yml" \ + exec -T --user 1000 "$SERVICE" "${CMD[@]}" + ``` + `-T` は必須(付けないとTTY割り当てを試みて、非対話環境では失敗しうる)。 +- コンテナが起動していない場合の対処は docker-ops Skillを参照。 diff --git a/.claude/skills/demo-recording/SKILL.md b/.claude/skills/demo-recording/SKILL.md new file mode 100644 index 0000000..aceda2a --- /dev/null +++ b/.claude/skills/demo-recording/SKILL.md @@ -0,0 +1,47 @@ +--- +name: demo-recording +description: >- + e2e/ ディレクトリで Playwright を使った動作デモGIFを録画する際に使う。 + 「デモGIFを録って」「動作を録画して」等の指示で使う。 +allowed-tools: Bash(npm:*), Bash(cd:*) +--- + +# demo-recording + +`e2e/` はE2Eテストではなく、動作デモGIF録画専用のディレクトリ(`frontend/` とは分離、 +CIには組み込まれない・手動実行のみ)。 + +## 前提 + +- `ffmpeg` がインストール済み(`brew install ffmpeg`) +- `frontend/` の開発サーバーが `http://localhost:5173` で起動していること + (フロントの起動は別途 `npm run dev` を `frontend/` で実行しておく。このSkillの + 対象外) + +## 手順 + +Playwrightブラウザのインストールは、`package-lock.json` で解決されたバージョン +(本リポジトリでは `playwright@1.61.1`)を使うため、必ず `npm install` の**後**に +行う(`npm install` 前に `npx playwright install` を実行すると、ローカルに無い +`playwright` をレジストリから取得してしまい、バージョンがずれる)。再現性を +厳密にしたい場合は `npm install` の代わりに `npm ci` を使う。 + +```bash +cd e2e +npm install +npm exec -- playwright install chromium # 初回のみ。ブラウザバイナリを取得 +npm run record # playwright test --project=chromium を実行。output/*.webm を生成 +npm run to-gif # scripts/to-gif.sh でwebm→GIF変換。docs/gif/*.gif を生成 +``` + +## 新規デモシナリオの追加 + +新しいシナリオを録画したい場合は `e2e/scenarios/` に `*.spec.ts` を追加する +(既存の `e2e/scenarios/jwt-flow.spec.ts` を参考にする)。シナリオ追加後に +上記の `npm run record` → `npm run to-gif` を実行する。 + +## 生成物の扱い + +- `e2e/output/`(録画中間ファイルの `.webm`)は `.gitignore` 対象。コミットしない。 +- `docs/gif/*.gif`(変換後のGIF)はREADME等に埋め込んで使うため、リポジトリに + 含める(コミット対象)。 diff --git a/.claude/skills/docker-ops/SKILL.md b/.claude/skills/docker-ops/SKILL.md new file mode 100644 index 0000000..d7ee1e2 --- /dev/null +++ b/.claude/skills/docker-ops/SKILL.md @@ -0,0 +1,89 @@ +--- +name: docker-ops +description: >- + docker/bin/docker-*.sh の使い方(env/up/down/exec 等)でコンテナを起動・停止・ + 操作する際、またはコンテナが起動しない等のトラブル対応をする際に使う。 +allowed-tools: Bash(docker:*), Bash(cd:*), Bash(find:*) +--- + +# docker-ops + +`docker/bin/*.sh` は各バックエンド・共通インフラのDocker操作を統一するラッパー +スクリプト。コンテナの起動・停止・破棄(`up`/`down`/`start`/`stop`)は必ずこれらを +経由する(`guard-bash.sh` が `docker stop` / `docker compose stop|kill|down` 等の +直接実行をブロックし、本Skillのラッパー使用へ誘導する)。 + +一方、起動済みコンテナに対する非対話の単発コマンド実行(`exec`)は +`guard-bash.sh` のブロック対象外であり、`docker compose -p showcase- -f +docker/local/app-/docker-compose.yml exec -T --user 1000 <コマンド>` +を直接使ってよい(`-T`必須。無いとTTY割り当てを試みて非対話環境で失敗しうる。 +backend-dispatch Skill参照)。 + +## 前提(初回のみ) + +```bash +cd docker +find ./bin -type f -exec chmod 755 {} + +bin/docker-common.sh env # 証明書・.envのセットアップ +``` + +## 起動順序 + +Docker ネットワーク `showcase` と nginx-proxy を作る `common` を先に起動し、 +その後で各バックエンドを起動する。順序を逆にすると起動に失敗する。 + +```bash +cd docker +bin/docker-common.sh up # 1. 共通コンテナ(nginx-proxy, showcaseネットワーク作成) +bin/docker-backends.sh up # 2. 全バックエンド一括起動 +# もしくは個別に: +bin/docker-go.sh up +bin/docker-java.sh up +bin/docker-php-cake.sh up +bin/docker-php-codeigniter.sh up +bin/docker-php-fuel.sh up +bin/docker-python.sh up +bin/docker-ruby.sh up +``` + +各 `docker-.sh up` は `.env` が無ければ `.env.example` から自動生成してから +`docker compose -p showcase- up -d --build --force-recreate` を実行する。 + +## コンテナに入る/破棄する + +```bash +X=go # 対応表(backend-dispatch Skill参照)の値に置き換える +"docker/bin/docker-$X.sh" exec # コンテナ内シェルに入る(Goはsh、他はbash) +"docker/bin/docker-$X.sh" down # そのバックエンドのコンテナ・イメージ・ボリュームを破棄 +docker/bin/docker-backends.sh down # 全バックエンド一括破棄 +``` + +`common` の操作は `up` / `down` に加えて `start` / `stop`(コンテナ破棄せず +起動・停止のみ)も使える: + +```bash +docker/bin/docker-common.sh start +docker/bin/docker-common.sh stop +``` + +## 破壊的操作への注意 + +- `down` は対象コンテナに加えて **イメージ・ボリュームも削除**する + (`--rmi all --volumes`)。 +- `docker-common.sh down` はさらに `docker/local/common/data/` と + `docker/local/common/logs/` ディレクトリも削除する。 +- 上記のためユーザーの明示的な指示なしに `down` を実行しない。コンテナの + 再起動だけで済む場合は `common` は `stop`→`start`、各バックエンドは + `up`(`--force-recreate` で再作成される)を使う。 + +## トラブルシュート + +- **バックエンドコンテナが起動しない**: `common` が起動しておらず `showcase` + ネットワークが無い可能性が高い。`docker/bin/docker-common.sh up` を先に実行する。 +- **状態確認**: `docker ps` でコンテナ名 `showcase-_` + (例: `showcase-go_go`, `showcase-go_nginx`)が上がっているか確認する。 +- **.env が古い/壊れている**: バックエンド側の `.env` は `docker/local/app-/.env` + にあり、削除して `docker/bin/docker-.sh up` を再実行すれば + `.env.example` から再生成される。 +- どのバックエンドがどのディレクトリ・スクリプトに対応するかは backend-dispatch + Skillを参照。 diff --git a/.claude/skills/git-flow/SKILL.md b/.claude/skills/git-flow/SKILL.md new file mode 100644 index 0000000..2b36775 --- /dev/null +++ b/.claude/skills/git-flow/SKILL.md @@ -0,0 +1,217 @@ +--- +name: git-flow +description: >- + 対応するGitHub Issueからfeatureブランチを作成し、developへのPRを作成する際に使う。 + 「Issueブランチを作って」「#Nのブランチを切って」「PRを出して」等の指示で使う。 + develop→mainの同期PRはこのSkillの対象外。 +allowed-tools: Bash(git:*), Bash(gh:*) +--- + +# git-flow + +このリポジトリ(bobtabo/authorization-showcase)のブランチ運用を統一する手順書。 + +## 前提 + +- 対応するGitHub Issue(番号 N)が既に存在すること。Issue番号を伴わないブランチ名 + (例: `feature/xxx-yyy`)は作らない。Issueがまだ無い場合は、既存Issueの書式 + (`[showcase] : <日本語要約>`、`## 背景` → `## タスク` の構成)に合わせて + 先にIssueを作成してから本Skillを使う。 +- 派生元ブランチは既定で `develop`。呼び出し時に別ブランチが明示された場合は + そちらを派生元として使う(例: hotfixを `main` から切る等、たまにある例外に対応)。 + +## 1. featureブランチ作成 + +```bash +set -euo pipefail +BASE=develop # 明示指定があればそちらに置き換える +N=34 # 対応するIssue番号に置き換える(実行前に書き換える。以下このSKILL.mdでは + # を「この値に置き換える」プレースホルダーとして表記する) + +git checkout "$BASE" +git pull --ff-only origin "$BASE" +gh issue view "$N" --repo bobtabo/authorization-showcase --json title -q .title +git checkout -b "feature/issue-$N" +``` + +`set -euo pipefail` により、Issueが存在せず `gh issue view` が失敗した場合は +`git checkout -b` を実行せずに停止する。`git pull` は `--ff-only` とし、 +ローカルとリモートが分岐している場合は自動マージせず停止する。 + +## 2. developへのPR作成 + +- base: `develop` / head: `feature/issue-$N`(派生元を変えた場合もbaseは`develop`) +- タイトル: `$TYPE(#$N): $SUMMARY` + (`TYPE`: feat/fix/docs/chore/refactor 等、conventional commit風。Issueのtypeを踏襲する) +- 本文: Summary / Changes / Closes(またはRefs) / 署名の4点を含める。Issueの一部のみ + 対応する場合(Issueの残タスクが残る場合)は `Closes #$N` ではなく `Refs #$N` とし、 + Issueを自動クローズさせない。 + +- コマンド(`N`/`TYPE`/`SUMMARY`/`CHANGES`/`LINK` に実際の値を設定してから実行する。 + この節のブロックは手順1とは別のシェルプロセスで実行される想定のため、`N`を + 再設定する。heredocはクォートしない `<&2 + exit 1 + } + if echo "$STATES" | grep -qE '^(FAILURE|ERROR)$'; then + echo "CIが失敗しました" >&2 + exit 1 + fi + if [ -n "$STATES" ] && ! echo "$STATES" | grep -qv '^SUCCESS$'; then + echo "全チェックSUCCESS" + break + fi + echo "[$i] pending: $STATES" + if [ "$i" -eq 40 ]; then + echo "タイムアウト: CIが完了しませんでした" >&2 + exit 1 + fi + sleep 15 +done +``` + +### 3.2 CodeRabbitの指摘への対応 + +「未返信」とは、CodeRabbit(`coderabbitai[bot]`)のtop-levelコメントのうち、 +`coderabbitai[bot]` 以外からの返信が付いていないものを指す(top-levelコメント数を +そのまま見ると、既に返信済みのものも数えてしまい判定を誤る): + +```bash +set -euo pipefail +N=34 # このfeatureブランチのIssue番号 +PR=$(gh pr view "feature/issue-$N" --repo bobtabo/authorization-showcase --json number -q .number) + +COMMENTS_URL="repos/bobtabo/authorization-showcase/pulls/$PR/comments" +TOP_IDS=$(gh api "$COMMENTS_URL" --paginate \ + --jq '.[] | select(.user.login=="coderabbitai[bot]" and .in_reply_to_id==null) | .id') +REPLIED_IDS=$(gh api "$COMMENTS_URL" --paginate \ + --jq '.[] | select(.in_reply_to_id != null and .user.login!="coderabbitai[bot]") | .in_reply_to_id') +# gh api の --jq でAPI応答を直接フィルタする(応答をシェル変数に保持してから +# echo | jq に通すと、本文中のエスケープシーケンスがechoで壊れてjqがパース +# エラーになることがあるため避ける) + +comm -23 <(echo "$TOP_IDS" | sort) <(echo "$REPLIED_IDS" | sort -u) +# 出力された各IDについて本文を確認する: +# gh api repos/bobtabo/authorization-showcase/pulls/comments/ --jq '.path, .line, .body' +``` + +未返信の指摘ごとに: + +1. 指摘内容を現在のコードと照らして検証する。既に対応済み/的外れ/このリポジトリの + 既知のスコープ外事項(Issue本文に明記済み等)なら、修正はせず理由を添えて返信する。 +2. 妥当な指摘は修正してコミット・プッシュする。 +3. 各コメントIDに返信する(本文はプレースホルダーのまま送信せず、実際のコミットSHAと + 修正内容を変数に入れてから渡す): + +```bash +set -euo pipefail +N=34 # このfeatureブランチのIssue番号 +PR=$(gh pr view "feature/issue-$N" --repo bobtabo/authorization-showcase --json number -q .number) +COMMENT_ID=1234567 # 3.2で洗い出した未返信コメントのID +COMMIT_SHA=$(git rev-parse --short HEAD) +FIX_SUMMARY="何をどう直したかを記述する" +REPLY_BODY="対応しました(${COMMIT_SHA})。${FIX_SUMMARY}" + +gh api "repos/bobtabo/authorization-showcase/pulls/$PR/comments/$COMMENT_ID/replies" \ + -f body="$REPLY_BODY" +``` + +### 3.3 再レビューのトリガーと収束判定 + +CodeRabbitは短時間に複数コミットが積まれると "reviews paused"(自動一時停止)になり、 +push しただけでは新しいレビューが走らないことがある。その場合は明示的に再トリガーする: + +```bash +set -euo pipefail +N=34 # このfeatureブランチのIssue番号 +PR=$(gh pr view "feature/issue-$N" --repo bobtabo/authorization-showcase --json number -q .number) +gh pr comment "$PR" --repo bobtabo/authorization-showcase --body "@coderabbitai review" +``` + +再トリガー後、3.1のCI待ちと3.2の未返信コメント抽出を繰り返す。 + +収束(完了)の判定基準は次の**両方**を満たすこと: +- 3.2の未返信コメント抽出結果が0件 +- `gh pr view "$PR" --repo bobtabo/authorization-showcase --json mergeable,mergeStateStatus` + が `mergeable: MERGEABLE` / `mergeStateStatus: CLEAN` + +目安として5ラウンド前後で収束しない場合は、無限にループさせず一旦打ち切り、 +未解決点を添えてユーザーに報告し、続行するかどうかの判断を仰ぐ。 + +収束したら最終状態(CI結果・対応した指摘の要約・PRリンク)をユーザーに報告する。 +**developへのマージはこのSkillでは行わない**(ユーザーの明示的な指示を待つ)。 + +## 禁止事項 + +- `develop` / `main` への直接push(featureブランチ経由のPRを必ず通す) +- `develop` → `main` のマージはこのSkillでは行わない(「develop → main 同期」PRとして別途手動で作成する) + +## 既知の制約 + +- 本SkillはSKILL.mdという「明示的に呼び出したときの手順書」であり、`allowed-tools` や + 現状の `.claude/hooks/guard-bash.sh` は `develop` / `main` への直接pushを機械的には + ブロックしない(`guard-bash.sh` が対象とするのは `push --force` / `reset --hard` 等の + 破壊的操作のみ)。保護ブランチへのpushを権限層で強制したい場合は、`guard-bash.sh` の + 拡張または GitHub 側のブランチ保護ルールの設定を別Issueとして検討する。 diff --git a/.claude/skills/new-backend-scaffold/SKILL.md b/.claude/skills/new-backend-scaffold/SKILL.md new file mode 100644 index 0000000..a52dcd3 --- /dev/null +++ b/.claude/skills/new-backend-scaffold/SKILL.md @@ -0,0 +1,216 @@ +--- +name: new-backend-scaffold +description: >- + 新しい言語/フレームワークのバックエンドをこのリポジトリに追加する際に使う。 + 既存7バックエンド(Go/Java/PHP×3/Python/Ruby)と同じ構成(README/.env.example/ + docker-compose.yml/CIワークフロー)で雛形を揃える。 +allowed-tools: Bash(git:*), Bash(cp:*), Bash(mkdir:*) +--- + +# new-backend-scaffold + +新規バックエンド ``(例: `node-express`)を、既存実装と同じ構成で追加する +手順。最小構成の `go-gin` を雛形として使う(アプリコンテナ1つ + nginxの2サービス +構成で、他バックエンドも基本的に同じ形)。 + +## 前提 + +- 事前に対応するGitHub Issueを作成し、git-flow Skillで `feature/issue-N` + ブランチを作成しておく。 +- 以降のコマンド例は `NAME` 変数に実際のバックエンド名を入れて使う + (`` をそのままシェルに渡すと `<`/`>` がリダイレクトと解釈され + 構文エラーになるため、必ず変数化する)。`NAME` はディレクトリ名・Docker + Composeのプロジェクト名・CIワークフローファイル名にそのまま使われるため、 + 既存7バックエンド同様のkebab-case(`^[a-z0-9]+(-[a-z0-9]+)*$`)にする。 + 各ブロックの冒頭で検証している。 +- **各手順のbashブロックは別々のシェルプロセスとして実行される想定**で、 + `NAME` はブロック間で共有されない。そのため各ブロックの先頭で毎回 + `NAME=<実際の値>` を(同じ値で)設定し直す。同一シェルセッション内で + 複数手順を続けて実行する場合は、2回目以降の再設定は害にならない。 +- 手順2〜4のコピー操作は、コピー先が既に存在する場合は実行前に停止する + (再実行で既存の変更を`cp`が上書きするのを防ぐ)。既存の生成物を作り直したい + 場合は、手動で削除するか意図的に上書きすることを確認した上で行う。 + +## 1. バックエンドソース(`backends/$NAME/`) + +```bash +NAME=node-express # 実際に追加するバックエンド名に置き換える +[[ "$NAME" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]] || { echo "invalid backend name: $NAME" >&2; exit 1; } + +if [ -e "backends/$NAME" ]; then + echo "backends/$NAME は既に存在します。上書きを避けるため中断します" >&2 + exit 1 +fi +mkdir -p "backends/$NAME" +``` + +- アプリのソース一式を配置する。 +- `backends/$NAME/README.md` を作成する(`backends/go-gin/README.md` の構成 + ―概要/起動方法/エンドポイント一覧―に合わせる)。 + +## 2. Docker実行環境(`docker/local/app-$NAME/`) + +`docker/local/app-go/` の中身を丸ごとコピーして書き換える。ディレクトリ自体を +`cp -r src dst` すると、`dst` が既に存在する場合は `dst/app-go/` のようにネストして +しまうため、コピー先を先に作ってから**中身**をコピーする。ただし `docker/local/app-go/.env` +(`.gitignore`対象。ホストでのローカル起動により生成された実際の認証情報・ +コンテナ名・ポート等を含みうる)と `docker/local/app-go/logs/`(実行時ログ)は +新バックエンド用の雛形に含めるべきではないため除外する(`.env`は docker-ops Skillの +通り、後で `docker/bin/docker-$NAME.sh up` を実行した際に `.env.example` から +自動生成されるため、ここで手動生成する必要はない): + +```bash +NAME=node-express # 手順1と同じ値 +[[ "$NAME" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]] || { echo "invalid backend name: $NAME" >&2; exit 1; } + +if [ -e "docker/local/app-$NAME" ]; then + echo "docker/local/app-$NAME は既に存在します。上書きを避けるため中断します" >&2 + exit 1 +fi +mkdir -p "docker/local/app-$NAME" +rsync -a --exclude='.env' --exclude='logs/' docker/local/app-go/ "docker/local/app-$NAME/" +mkdir -p "docker/local/app-$NAME/logs" +``` + +書き換える点: + +- `docker-compose.yml` + - `name: showcase-app-$NAME` + - サービス名・イメージのbuild文脈(`go/` ディレクトリ相当)を言語用に差し替え + - アプリ側の `volumes` を `./../../../backends/$NAME:/var/www/${APP_NAME}` に + - `networks.showcase.external: true` はそのまま維持(`common` が作るネットワーク + に参加する) + - `nginx` サービス定義(`VIRTUAL_HOST` / `VIRTUAL_PORT` 等の環境変数連携)は + ほぼそのまま流用可 +- `.env.example` + - `APP_NAME=app-$NAME` + - `SERVER_NAME=apis.showcase-$NAME.dev` + - `NGINX_CONTAINER=showcase-${NAME}_nginx` + - 言語コンテナの `_CONTAINER=showcase-${NAME}_` を追加 + - デバッグポート等、言語固有の変数を追加 +- `nginx/`(`Dockerfile` / `nginx.conf` / `templates/default.conf.template`)は + ほぼそのまま流用できる(`APP_PATH` 等の環境変数経由でルーティングするため) + +## 3. Dockerラッパースクリプト(`docker/bin/docker-$NAME.sh`) + +`docker/bin/docker-go.sh` をコピーして書き換える。 + +```bash +NAME=node-express # 手順1と同じ値 +[[ "$NAME" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]] || { echo "invalid backend name: $NAME" >&2; exit 1; } + +if [ -e "docker/bin/docker-$NAME.sh" ]; then + echo "docker/bin/docker-$NAME.sh は既に存在します。上書きを避けるため中断します" >&2 + exit 1 +fi +cp docker/bin/docker-go.sh "docker/bin/docker-$NAME.sh" +chmod 755 "docker/bin/docker-$NAME.sh" +``` + +書き換える点: `cd` 先を `app-$NAME`、`-p showcase-$NAME`、`exec` で入る +サービス名・シェル(bash推奨、必要ならsh)。 + +さらに `docker/bin/docker-backends.sh` の `run()` に1行追加する。 +この行は `docker-backends.sh` 自身のスクリプト内に書く固定のコードであり、 +その場では `NAME` 変数は存在しないため、`$NAME` ではなく**実際のバックエンド名を +直接書く**(例: `node-express`): + +```bash + bash "${SCRIPT_DIR}/docker-node-express.sh" "${ARG}" +``` + +## 4. CIワークフロー(`.github/workflows/$NAME-ci.yml`) + +`.github/workflows/go-gin-ci.yml` をコピーして書き換える。 + +```bash +NAME=node-express # 手順1と同じ値 +[[ "$NAME" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]] || { echo "invalid backend name: $NAME" >&2; exit 1; } + +if [ -e ".github/workflows/$NAME-ci.yml" ]; then + echo ".github/workflows/$NAME-ci.yml は既に存在します。上書きを避けるため中断します" >&2 + exit 1 +fi +cp .github/workflows/go-gin-ci.yml ".github/workflows/$NAME-ci.yml" +``` + +書き換える点: +- ファイル先頭のコメント(`# Go(Gin)CI ワークフロー` / `# developブランチの + backends/go-gin/** 変更時に CI を実行`)と `name: Go(Gin)CI` を、追加する + 言語・バックエンド名に合わせて書き換える(コピー直後はGo向けの文言が残っている) +- `paths: - 'backends/$NAME/**'` +- `branches-ignore: ['feature/issue-*']` と `concurrency.group: + shared-authserver-tunnel` は変更しない(全バックエンド共有のngrok/LocalStack + 輻輳防止のため) +- 言語ツールチェインのセットアップステップ(`actions/setup-go` 相当) +- `Resolve AUTH_SERVER_URL` ステップは他バックエンドと同一ロジックのため、 + そのまま流用する(LocalStackのAPI Gateway IDをngrok経由で動的取得する仕組み) +- テスト実行コマンド・`working-directory: backends/$NAME` のみ差し替え + +新規追加したワークフローファイルは `main` にマージされるまで `workflow_dispatch` +での手動発火ができない(GitHubの仕様。詳細は backend-ci-trigger Skillの当該 +注記を参照)。featureブランチ段階でのCI確認は、develop→main同期後まで待つか、 +ワークフローファイルのみ先に反映する必要がある。 + +## 5. ドキュメント更新 + +- `backends/README.md`: 実装スタック一覧の表に1行追加(状態は `🚧 予定` または + `✅ 完了`) +- ルート `README.md`: + - 「システム構造」のツリー(`backends/` 配下の一覧)に追加 + - 「6. バックエンドの初期設定」に `6.x $NAME` の節を追加 +- 他Skillの登録先(更新を忘れるとSkill経由で新バックエンドを選択・起動・CI発火 + できない): + - `.claude/skills/backend-dispatch/SKILL.md` の対応表に1行追加 + - `.claude/skills/backend-ci-trigger/SKILL.md` のワークフロー対応表に1行追加 + - `.claude/skills/docker-ops/SKILL.md` の「起動順序」の個別列挙に + `docker/bin/docker-$NAME.sh up` を追加 + +以下7ファイルすべてに `$NAME` が登場することを最終確認する: +`docker/bin/docker-backends.sh`(手順3)、`.github/workflows/$NAME-ci.yml` +(手順4)、`backends/README.md` / `README.md`(本節)、 +`.claude/skills/{backend-dispatch,backend-ci-trigger,docker-ops}/SKILL.md` +(本節)。1ファイルでも欠けていたら止まるように、ファイルごとに個別検査する +(`grep -rl` は一致したファイルのみ表示するため、欠落の見逃しに気づけない)。 + +```bash +set -euo pipefail +NAME=node-express # 手順1と同じ値 +[[ "$NAME" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]] || { echo "invalid backend name: $NAME" >&2; exit 1; } + +files=( + "docker/bin/docker-backends.sh" + ".github/workflows/$NAME-ci.yml" + "backends/README.md" + "README.md" + ".claude/skills/backend-dispatch/SKILL.md" + ".claude/skills/backend-ci-trigger/SKILL.md" + ".claude/skills/docker-ops/SKILL.md" +) + +for f in "${files[@]}"; do + if ! grep -q -- "$NAME" "$f"; then + echo "登録漏れ: $f に '$NAME' が見つかりません" >&2 + exit 1 + fi +done +echo "7ファイルすべてに登録を確認しました" +``` + +## 6. 動作確認 + +`showcase` ネットワークが無い(= `docker/bin/docker-common.sh up` を一度も +実行していない)クリーンな環境では、先に共通コンテナを起動しておく必要がある +(docker-ops Skill参照)。 + +```bash +NAME=node-express # 手順1と同じ値 +[[ "$NAME" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]] || { echo "invalid backend name: $NAME" >&2; exit 1; } + +docker/bin/docker-common.sh up # showcaseネットワーク・nginx-proxyが無ければ起動 +"docker/bin/docker-$NAME.sh" up +"docker/bin/docker-$NAME.sh" exec # コンテナに入り疎通確認 +``` + +CIの手動発火・確認は backend-ci-trigger Skillを参照。ここまで確認できたら +git-flow Skillでdevelopへのpullリクエストを作成する。 diff --git a/.gitignore b/.gitignore index 82fae1d..e64debc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ docker/local/.env .claude/* !.claude/settings.json !.claude/hooks/ +!.claude/skills/ .claude/settings.local.json /backends/go-gin/tmp/ /backends/java-springboot/.gradle/ diff --git a/e2e/README.md b/e2e/README.md index baa8663..7475c8d 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -17,14 +17,18 @@ e2e/ ## :white_check_mark: 前提条件 - ffmpeg がインストール済みであること(`brew install ffmpeg`) -- Playwright ブラウザのインストール: `npx playwright install chromium` - `frontend/` の開発サーバーが `http://localhost:5173` で起動していること ## :rocket: 使い方 +Playwright ブラウザのインストールは `package-lock.json` で解決されたバージョンを +使うため、`npm install` の**後**に行ってください(先に実行すると、ローカルに +無い `playwright` をレジストリから取得してしまいバージョンがずれます)。 + ```bash cd e2e npm install +npm exec -- playwright install chromium # 初回のみ npm run record # 録画(output/*.webm が生成される) npm run to-gif # GIF 変換(docs/gif/*.gif が生成される) ```