diff --git a/templates/README.md b/templates/README.md index b5f8d5ee..b808be17 100644 --- a/templates/README.md +++ b/templates/README.md @@ -6,12 +6,14 @@ ### ワークフロー(`workflows/`) -| テンプレート | 対象 | 自動適用条件 | -| --------------------------- | ------------------------------- | -------------------------------- | -| `dependabot-auto-merge.yml` | Dependabot を使う全プロジェクト | dependabot.yml が存在する場合 | -| `label-sync.yml` | 全プロジェクト | 常に推奨 | -| `stale.yml` | Issue/PR が多いプロジェクト | 常に推奨 | -| `terraform-drift.yml` | Terraform プロジェクト | Terraform ファイルが存在する場合 | +| テンプレート | 対象 | 自動適用条件 | +| --------------------------- | ------------------------------- | --------------------------------- | +| `dependabot-auto-merge.yml` | Dependabot を使う全プロジェクト | dependabot.yml が存在する場合 | +| `label-sync.yml` | 全プロジェクト | 常に推奨 | +| `stale.yml` | Issue/PR が多いプロジェクト | 常に推奨 | +| `terraform-drift.yml` | Terraform プロジェクト | Terraform ファイルが存在する場合 | +| `e2e-playwright.yml` | Playwright E2E テスト | test:e2e スクリプトが存在する場合 | +| `claude.yml` | Claude Code 連携 | 常に推奨 | ### pre-commit(`pre-commit-config-*.yaml`) @@ -34,6 +36,25 @@ | `SECURITY.md` | セキュリティポリシー | 中 | | `CONTRIBUTING.md` | コントリビューションガイド | 低 | +### コードスタイル(Prettier / lint-staged) + +| テンプレート | いつ使う | +| --------------------------------- | ---------------------------------------------------- | +| `prettierrc-base.json` | **標準**: printWidth 80、arrowParens "avoid" | +| `prettierrc-wide.json` | **ワイド**: printWidth 120、arrowParens "always" | +| `prettierignore` | **全プロジェクト**: 共通の除外パターン | +| `lintstagedrc-eslint.json` | **ESLint + Prettier**: lint + format on staged files | +| `lintstagedrc-biome.json` | **Biome**: check + format on staged files | +| `lintstagedrc-prettier-only.json` | **Prettier のみ**: format on staged files | + +### Git Hooks(`husky/`) + +| テンプレート | 内容 | +| ------------ | --------------------------------------------------- | +| `pre-commit` | lint-staged + ファイル長チェック | +| `commit-msg` | commitlint(Conventional Commits) | +| `pre-push` | typecheck + lint + test(スクリプト存在時のみ実行) | + ### その他 | テンプレート | 対象 | diff --git a/templates/husky/commit-msg b/templates/husky/commit-msg new file mode 100755 index 00000000..f0abd676 --- /dev/null +++ b/templates/husky/commit-msg @@ -0,0 +1,3 @@ +#!/bin/sh + +npx --no -- commitlint --edit "$1" diff --git a/templates/husky/pre-commit b/templates/husky/pre-commit new file mode 100755 index 00000000..9979f130 --- /dev/null +++ b/templates/husky/pre-commit @@ -0,0 +1,9 @@ +#!/bin/sh + +# lint-staged: フォーマット・リント +npx lint-staged || exit 1 + +# ファイル長チェック(script/check-file-length.sh が存在する場合のみ) +if [ -f script/check-file-length.sh ]; then + bash script/check-file-length.sh || exit 1 +fi diff --git a/templates/husky/pre-push b/templates/husky/pre-push new file mode 100755 index 00000000..4f772d93 --- /dev/null +++ b/templates/husky/pre-push @@ -0,0 +1,26 @@ +#!/bin/sh + +echo "Pre-push checks..." + +# TypeScript 型チェック(スクリプト未定義時はスキップ) +if npm run typecheck --if-present; then + echo "-> TypeScript check passed" +else + echo "TypeScript check failed"; exit 1 +fi + +# Lint(スクリプト未定義時はスキップ) +if npm run lint --if-present; then + echo "-> Lint passed" +else + echo "Lint failed"; exit 1 +fi + +# テスト(スクリプト未定義時はスキップ) +if npm run --if-present test; then + echo "-> Tests passed" +else + echo "Tests failed"; exit 1 +fi + +echo "All pre-push checks passed" diff --git a/templates/lintstagedrc-biome.json b/templates/lintstagedrc-biome.json new file mode 100644 index 00000000..a4125135 --- /dev/null +++ b/templates/lintstagedrc-biome.json @@ -0,0 +1,5 @@ +{ + "*.{ts,tsx,js,jsx}": ["biome check --fix --no-errors-on-unmatched"], + "*.{json,md}": ["biome format --write --no-errors-on-unmatched"], + "*.{yaml,yml}": ["prettier --write"] +} diff --git a/templates/lintstagedrc-eslint.json b/templates/lintstagedrc-eslint.json new file mode 100644 index 00000000..af752ac7 --- /dev/null +++ b/templates/lintstagedrc-eslint.json @@ -0,0 +1,5 @@ +{ + "*.{ts,tsx}": ["eslint --fix", "prettier --write"], + "*.{js,jsx,mjs,cjs}": ["eslint --fix", "prettier --write"], + "*.{json,css,md,yml,yaml}": ["prettier --write"] +} diff --git a/templates/lintstagedrc-prettier-only.json b/templates/lintstagedrc-prettier-only.json new file mode 100644 index 00000000..deecafba --- /dev/null +++ b/templates/lintstagedrc-prettier-only.json @@ -0,0 +1,3 @@ +{ + "*.{js,jsx,ts,tsx,css,md,json,yaml,yml}": ["prettier --write"] +} diff --git a/templates/prettierignore b/templates/prettierignore new file mode 100644 index 00000000..662c7eba --- /dev/null +++ b/templates/prettierignore @@ -0,0 +1,34 @@ +# Dependencies +node_modules/ + +# Build outputs +.next/ +out/ +dist/ +build/ +coverage/ + +# Lock files +package-lock.json +pnpm-lock.yaml +yarn.lock + +# Environment +.env +.env.* + +# IDE / OS +.vscode/ +.idea/ +.DS_Store + +# Claude Code +.claude/settings.local.json + +# Terraform +*.tfstate +*.tfstate.* +.terraform/ + +# Supabase +supabase/.temp/ diff --git a/templates/prettierrc-base.json b/templates/prettierrc-base.json new file mode 100644 index 00000000..b7e4e9d9 --- /dev/null +++ b/templates/prettierrc-base.json @@ -0,0 +1,28 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "useTabs": false, + "trailingComma": "all", + "printWidth": 80, + "endOfLine": "lf", + "arrowParens": "avoid", + "bracketSpacing": true, + "bracketSameLine": false, + "quoteProps": "as-needed", + "jsxSingleQuote": true, + "overrides": [ + { + "files": "*.json", + "options": { + "singleQuote": false + } + }, + { + "files": "*.md", + "options": { + "proseWrap": "preserve" + } + } + ] +} diff --git a/templates/prettierrc-wide.json b/templates/prettierrc-wide.json new file mode 100644 index 00000000..e1d8bc22 --- /dev/null +++ b/templates/prettierrc-wide.json @@ -0,0 +1,28 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "useTabs": false, + "trailingComma": "all", + "printWidth": 120, + "endOfLine": "lf", + "arrowParens": "always", + "bracketSpacing": true, + "bracketSameLine": false, + "quoteProps": "as-needed", + "jsxSingleQuote": false, + "overrides": [ + { + "files": "*.json", + "options": { + "singleQuote": false + } + }, + { + "files": "*.md", + "options": { + "proseWrap": "preserve" + } + } + ] +} diff --git a/templates/workflows/claude.yml b/templates/workflows/claude.yml new file mode 100644 index 00000000..7c04259d --- /dev/null +++ b/templates/workflows/claude.yml @@ -0,0 +1,104 @@ +# Claude Code ワークフロー +# +# Issue や PR で @claude をメンションすると Claude Code が応答する。 +# Bot からのイベントは自動除外。 +# PR イベント(pull_request_review_comment / pull_request_review)では Draft PR も除外。 +# issue_comment イベントでは Draft 判定不可のため除外なし。 +# +# 使い方: +# .github/workflows/claude.yml にコピーして配置 +# リポジトリ Secret に CLAUDE_CODE_OAUTH_TOKEN を設定 +# +# カスタマイズ: +# - timeout-minutes: タイムアウト(デフォルト: 20分) +# - allowed_tools: Claude に許可するツール +# - claude_args: 追加の system prompt 等 +# +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened] + pull_request_review: + types: [submitted] + +permissions: {} + +concurrency: + # イベント種別 + トリガーのコメント/レビューIDを含めることで、 + # 同一 PR/Issue 内でも異なるメンションが互いをキャンセルしない + group: >- + claude-${{ github.repository }}-${{ + github.event.issue.number + || github.event.pull_request.number + || github.run_id + }}-${{ + github.event.comment.id + || github.event.review.id + || github.run_id + }} + cancel-in-progress: false + +jobs: + claude: + # Bot 全般を除外し、信頼済み Association のみ許可 + # OWNER / MEMBER / COLLABORATOR: リポジトリ管理者・チームメンバー・招待コラボレーター + if: | + github.event.sender.type != 'Bot' && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), + github.event.comment.author_association || + github.event.review.author_association || + github.event.issue.author_association + ) && + ( + (github.event_name == 'issue_comment' && contains(github.event.comment.body || '', '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body || '', '@claude') && + github.event.pull_request.draft == false) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body || '', '@claude') && + github.event.pull_request.draft == false) || + (github.event_name == 'issues' && (contains(github.event.issue.body || '', '@claude') || contains(github.event.issue.title || '', '@claude'))) + ) + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: write + pull-requests: write + issues: write + id-token: write + actions: read + checks: read + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@6e2bd52842c65e914eba5c8badd17560bd26b5de # v1.0.89 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + additional_permissions: | + actions: read + checks: read + + settings: | + { + "permissions": { + "allowedTools": [ + "Bash(gh:*)", + "Bash(git:*)", + "Bash(npm:*)", + "Bash(pnpm:*)", + "Bash(npx:*)" + ] + } + } + + claude_args: | + --system-prompt "When working on GitHub Issues (not PRs), after completing all code changes and pushing to a branch, you MUST create a Pull Request by running 'gh pr create' command. Do not just provide a link - actually execute the gh pr create command to create the PR automatically." diff --git a/templates/workflows/e2e-playwright.yml b/templates/workflows/e2e-playwright.yml new file mode 100644 index 00000000..d14c6f9f --- /dev/null +++ b/templates/workflows/e2e-playwright.yml @@ -0,0 +1,99 @@ +# E2E Playwright テストワークフロー +# +# Playwright を使った E2E テストを実行する再利用可能ワークフロー。 +# Next.js アプリケーションのビルド → Playwright テスト → レポートアップロード。 +# +# 使い方: +# .github/workflows/e2e-playwright.yml にコピーして配置 +# package.json に test:e2e スクリプトを定義 +# +# カスタマイズ: +# - NODE_VERSION: Node.js バージョン(デフォルト: 22) +# - PACKAGE_MANAGER: npm / pnpm(デフォルト: npm) +# - PLAYWRIGHT_VERSION: Playwright コンテナバージョン +# - E2E_COMMAND: テスト実行コマンド +# - WORKING_DIRECTORY: アプリケーションのルート +# +name: E2E Tests + +on: + workflow_call: + inputs: + node-version: + description: 'Node.js version' + type: string + default: '22' + package-manager: + description: 'Package manager (npm or pnpm)' + type: string + default: 'npm' + e2e-command: + description: 'E2E test command' + type: string + default: 'npm run test:e2e' + working-directory: + description: 'Working directory for the app' + type: string + default: '.' + build-command: + description: 'Build command (empty to skip)' + type: string + default: 'npm run build' + +# スタンドアロンで使う場合は上記 workflow_call を以下に置き換える: +# push: +# branches: [main] +# pull_request: +# branches: [main] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-e2e-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + e2e: + runs-on: ubuntu-latest + timeout-minutes: 30 + defaults: + run: + working-directory: ${{ inputs.working-directory || '.' }} + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Setup pnpm + if: inputs.package-manager == 'pnpm' + uses: pnpm/action-setup@a7487c7e89a18df77bdb2544e188270c5d72bfce # v4 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: ${{ inputs.node-version || '22' }} + cache: ${{ inputs.package-manager || 'npm' }} + + - name: Install dependencies + run: ${{ inputs.package-manager == 'pnpm' && 'pnpm install --frozen-lockfile' || 'npm ci' }} + + - name: Build + if: inputs.build-command != '' + run: ${{ inputs.build-command || 'npm run build' }} + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Run E2E tests + run: ${{ inputs.e2e-command || 'npm run test:e2e' }} + + - name: Upload Playwright report + if: ${{ !cancelled() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: playwright-report + path: | + ${{ inputs.working-directory || '.' }}/playwright-report/ + ${{ inputs.working-directory || '.' }}/test-results/ + retention-days: 14