diff --git a/.claude/commands/config-base-sync-update.md b/.claude/commands/config-base-sync-update.md index cc1e8901..d30b75a0 100644 --- a/.claude/commands/config-base-sync-update.md +++ b/.claude/commands/config-base-sync-update.md @@ -64,8 +64,64 @@ git status --porcelain If there are uncommitted changes: +### 4.1: Check if only devcontainer.json is modified + +Check if the only modified file is `.devcontainer/devcontainer.json`: + +```bash +MODIFIED_FILES=$(git status --porcelain | awk '{print $2}') +``` + +If only `.devcontainer/devcontainer.json` is modified: + +### 4.2: Check if change is version-only + +Analyze the diff to determine if it's only an image version change: + +```bash +git diff .devcontainer/devcontainer.json +``` + +Check if: + +1. The only change is in the `"image"` field +2. The change is a version number update (e.g., `ghcr.io/keito4/config-base:1.13.1` → `ghcr.io/keito4/config-base:1.15.0`) +3. No other fields are modified + +**Detection logic**: + +- Count the number of changed lines (excluding +/- prefixes) +- Verify all changes match the pattern: `"image": "ghcr.io/keito4/config-base:X.Y.Z"` +- Ensure both old and new versions point to the same registry and repository + +If the change is **version-only**: + +- Report: "✅ Detected version-only change in devcontainer.json (auto-overwrite enabled)" +- Show the version change: `X.Y.Z → target-version` +- Automatically discard the change and continue: + ```bash + git restore .devcontainer/devcontainer.json + ``` +- Proceed to Step 5 + +If the change includes **other modifications** (e.g., features, mounts, settings): + +- Report error: "❌ Uncommitted changes detected in .devcontainer/devcontainer.json" +- Show the non-version changes +- Suggest: "Please commit or stash these changes before updating" +- Stop execution + +### 4.3: Handle other uncommitted files + +If there are uncommitted changes in **other files** (not just devcontainer.json): + - Report error: "Uncommitted changes detected. Please commit or stash changes before updating." - List the uncommitted files +- Suggested actions: + 1. Review the changes: `git diff ` + 2. Commit the changes: `git add && git commit -m "your message"` + 3. Or stash the changes: `git stash` + 4. Then re-run this command - Stop execution ## Step 5: Create Update Branch diff --git a/.codex/devcontainer-recommendations.md b/.claude/devcontainer-recommendations.md similarity index 100% rename from .codex/devcontainer-recommendations.md rename to .claude/devcontainer-recommendations.md diff --git a/.codex/prompts/setup-recommended-ci.md b/.codex/prompts/setup-recommended-ci.md index 1c339892..bd79c55b 100644 --- a/.codex/prompts/setup-recommended-ci.md +++ b/.codex/prompts/setup-recommended-ci.md @@ -216,6 +216,135 @@ jobs: run: npm run build ``` +### CI最適化: Path Filters による実行最適化 + +**目的**: 変更されたファイルに応じて必要なジョブのみを実行することで、CIの実行時間を短縮し、コストを削減します。 + +#### 実装方法 + +GitHub Actionsの`on.push.paths`と`on.pull_request.paths`フィルター、および`dorny/paths-filter`アクションを組み合わせて使用します。 + +##### 1. ワークフローレベルでのフィルタリング + +ワークフロー全体をスキップするには、`on`セクションで`paths`を指定します: + +```yaml +on: + pull_request: + paths: + - '**.js' + - '**.ts' + - '**.mjs' + - '**.cjs' + - '**.json' + - '**.sh' + - '**.bats' + - '.github/workflows/**' + - 'package.json' + - 'package-lock.json' + - '.eslintrc.*' + - '.prettierrc.*' + - 'tsconfig.json' + push: + branches: [main, master] + paths: + - '**.js' + - '**.ts' + # ... 同様のパターン +``` + +##### 2. ジョブレベルでの細かい制御 + +各ジョブを変更内容に応じて実行するには、`dorny/paths-filter`を使用します: + +```yaml +jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + outputs: + code: ${{ steps.filter.outputs.code }} + scripts: ${{ steps.filter.outputs.scripts }} + workflows: ${{ steps.filter.outputs.workflows }} + dependencies: ${{ steps.filter.outputs.dependencies }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + code: + - '**.js' + - '**.ts' + - 'src/**' + - 'test/**/*.test.{js,ts}' + scripts: + - '**.sh' + - 'script/**' + workflows: + - '.github/workflows/**' + dependencies: + - 'package.json' + - 'package-lock.json' + + lint: + needs: changes + if: needs.changes.outputs.code == 'true' || needs.changes.outputs.dependencies == 'true' + # ... lintジョブの内容 + + test: + needs: changes + if: needs.changes.outputs.code == 'true' || needs.changes.outputs.dependencies == 'true' + # ... testジョブの内容 + + actionlint: + needs: changes + if: needs.changes.outputs.workflows == 'true' + # ... actionlintジョブの内容 +``` + +##### 3. Quality Gate での skipped 状態の処理 + +ジョブがスキップされた場合も成功として扱うように、Quality Gateを更新します: + +```yaml +quality-gate: + needs: [changes, lint, test, integration-test, actionlint] + if: always() + steps: + - name: Verify all checks passed + run: | + function check_result() { + local result=$1 + [[ "$result" == "success" || "$result" == "skipped" ]] + } + + if ! check_result "${{ needs.lint.result }}"; then + echo "::error::Lint & Format failed" + exit 1 + fi + # ... 他のジョブも同様にチェック +``` + +#### Path Filters のベストプラクティス + +| 対象ワークフロー | 推奨フィルター | 理由 | +| ---------------------- | ----------------------------------------------------- | ---------------------------------------- | +| **CI Pipeline** | コード、設定ファイル、ワークフロー自体 | ドキュメントのみの変更でテストをスキップ | +| **Docker Image Build** | `.devcontainer/**`, `npm/global.json`, `package.json` | DevContainer関連の変更時のみビルド | +| **Security Scans** | コード、依存関係ファイル | セキュリティに影響する変更のみスキャン | +| **Documentation** | `**.md`, `docs/**` | ドキュメント変更時のみデプロイ | + +#### 効果測定 + +Path Filtersを導入すると、以下のような効果が期待できます: + +- ドキュメントのみの変更: CI実行時間 **90%削減** (10分 → 1分) +- DevContainer変更なし: Docker Image Build **スキップ** (45分 → 0分) +- ワークフローのみの変更: Lint/Test **スキップ**、Actionlintのみ実行 + +**注意**: mainブランチへのpushでは、すべてのチェックを実行することを推奨します(path filtersを適用しない、または緩めに設定)。 + ### フル構成版 (Next.js + Supabase プロジェクト向け) Elu-co-jp リポジトリから参照: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af252ab2..86185d86 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,18 +2,82 @@ name: CI on: pull_request: + paths: + - '**.js' + - '**.ts' + - '**.mjs' + - '**.cjs' + - '**.json' + - '**.sh' + - '**.bats' + - '.github/workflows/**' + - 'package.json' + - 'package-lock.json' + - '.eslintrc.*' + - '.prettierrc.*' + - 'tsconfig.json' push: branches: [main, master] + paths: + - '**.js' + - '**.ts' + - '**.mjs' + - '**.cjs' + - '**.json' + - '**.sh' + - '**.bats' + - '.github/workflows/**' + - 'package.json' + - 'package-lock.json' + - '.eslintrc.*' + - '.prettierrc.*' + - 'tsconfig.json' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + code: ${{ steps.filter.outputs.code }} + scripts: ${{ steps.filter.outputs.scripts }} + workflows: ${{ steps.filter.outputs.workflows }} + dependencies: ${{ steps.filter.outputs.dependencies }} + steps: + - uses: actions/checkout@v4 + + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + code: + - '**.js' + - '**.ts' + - '**.mjs' + - '**.cjs' + - 'src/**' + - 'test/**/*.test.{js,ts}' + - 'tsconfig.json' + scripts: + - '**.sh' + - 'script/**' + - 'test/integration/**' + workflows: + - '.github/workflows/**' + dependencies: + - 'package.json' + - 'package-lock.json' + lint: name: Lint & Format runs-on: ubuntu-latest timeout-minutes: 10 + needs: changes + if: needs.changes.outputs.code == 'true' || needs.changes.outputs.scripts == 'true' || needs.changes.outputs.dependencies == 'true' steps: - uses: actions/checkout@v4 @@ -41,6 +105,8 @@ jobs: name: Unit Tests runs-on: ubuntu-latest timeout-minutes: 15 + needs: changes + if: needs.changes.outputs.code == 'true' || needs.changes.outputs.dependencies == 'true' steps: - uses: actions/checkout@v4 @@ -87,6 +153,8 @@ jobs: name: Integration Tests runs-on: ubuntu-latest timeout-minutes: 15 + needs: changes + if: needs.changes.outputs.scripts == 'true' || needs.changes.outputs.dependencies == 'true' steps: - uses: actions/checkout@v4 @@ -118,6 +186,8 @@ jobs: name: Workflow Lint runs-on: ubuntu-latest timeout-minutes: 5 + needs: changes + if: needs.changes.outputs.workflows == 'true' steps: - uses: actions/checkout@v4 @@ -131,7 +201,7 @@ jobs: name: Quality Gate runs-on: ubuntu-latest timeout-minutes: 5 - needs: [lint, test, integration-test, actionlint] + needs: [changes, lint, test, integration-test, actionlint] if: always() steps: - name: Check job results @@ -154,9 +224,35 @@ jobs: env: LINT_RESULT: ${{ needs.lint.result }} TEST_RESULT: ${{ needs.test.result }} + INTEGRATION_RESULT: ${{ needs.integration-test.result }} + ACTIONLINT_RESULT: ${{ needs.actionlint.result }} run: | - if [ "$LINT_RESULT" != "success" ] || [ "$TEST_RESULT" != "success" ]; then - echo "::error::Quality gate failed - lint or test jobs did not succeed" + # skipped は成功扱い(該当ファイルの変更がない場合) + function check_result() { + local result=$1 + [[ "$result" == "success" || "$result" == "skipped" ]] + } + + FAILED=false + if ! check_result "$LINT_RESULT"; then + echo "::error::Lint & Format failed" + FAILED=true + fi + if ! check_result "$TEST_RESULT"; then + echo "::error::Unit Tests failed" + FAILED=true + fi + if ! check_result "$INTEGRATION_RESULT"; then + echo "::error::Integration Tests failed" + FAILED=true + fi + if ! check_result "$ACTIONLINT_RESULT"; then + echo "::error::Workflow Lint failed" + FAILED=true + fi + + if [ "$FAILED" = true ]; then + echo "::error::Quality gate failed - one or more jobs did not succeed" exit 1 fi diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index cd0aa80c..08a1107f 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -3,6 +3,12 @@ name: Build and Release DevContainer Image on: push: branches: [main] + paths: + - '.devcontainer/**' + - 'npm/global.json' + - 'package.json' + - 'package-lock.json' + - '.github/workflows/docker-image.yml' workflow_dispatch: inputs: release_mode: diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index deb730a3..f6670095 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -3,8 +3,28 @@ name: Security Scans on: push: branches: [main] + paths: + - '**.js' + - '**.ts' + - '**.mjs' + - '**.cjs' + - '**.sh' + - 'package.json' + - 'package-lock.json' + - 'npm/global.json' + - '.github/workflows/security.yml' pull_request: branches: [main] + paths: + - '**.js' + - '**.ts' + - '**.mjs' + - '**.cjs' + - '**.sh' + - 'package.json' + - 'package-lock.json' + - 'npm/global.json' + - '.github/workflows/security.yml' schedule: - cron: '0 5 * * *' diff --git a/.github/workflows/templates/README.md b/.github/workflows/templates/README.md index 04cb0b28..1cf4a7c5 100644 --- a/.github/workflows/templates/README.md +++ b/.github/workflows/templates/README.md @@ -209,6 +209,84 @@ For npm registry instead of GitHub Packages: run: npx semantic-release ``` +## Path Filters for CI Optimization + +Path filtersを使用することで、変更されたファイルに応じて必要なワークフローやジョブのみを実行し、CI実行時間とコストを削減できます。 + +### ワークフローレベルのフィルタリング + +ワークフロー全体をスキップするには、`on`セクションでpathsを指定します: + +```yaml +on: + pull_request: + paths: + - '**.js' + - '**.ts' + - 'src/**' + - 'package.json' + - '.github/workflows/**' + push: + branches: [main] + # mainブランチでは全チェックを実行(pathsを指定しない) +``` + +### ジョブレベルのフィルタリング + +より細かい制御には`dorny/paths-filter`アクションを使用します: + +```yaml +jobs: + changes: + runs-on: ubuntu-latest + outputs: + code: ${{ steps.filter.outputs.code }} + docs: ${{ steps.filter.outputs.docs }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + code: + - 'src/**' + - 'test/**' + docs: + - '**.md' + - 'docs/**' + + test: + needs: changes + if: needs.changes.outputs.code == 'true' + # ... testジョブ + + deploy-docs: + needs: changes + if: needs.changes.outputs.docs == 'true' + # ... ドキュメントデプロイジョブ +``` + +### 推奨フィルターパターン + +| ワークフロー | 推奨パス | 理由 | +| ------------- | ---------------------------------------------------- | -------------------------------------------------- | +| CI Pipeline | `**.{js,ts}`, `package.json`, `src/**`, `test/**` | コード変更時のみテスト実行 | +| Docker Build | `.devcontainer/**`, `Dockerfile`, `package.json` | コンテナ関連の変更時のみビルド | +| Documentation | `**.md`, `docs/**` | ドキュメント変更時のみデプロイ | +| Security Scan | `**.{js,ts}`, `package-lock.json`, `npm/global.json` | セキュリティリスクのあるファイル変更時のみスキャン | + +### 効果測定例 + +- **ドキュメントのみの変更**: CI実行時間 90%削減 (10分 → 1分) +- **Docker関連以外の変更**: イメージビルド スキップ (45分 → 0分) +- **コード変更なし**: テストスキップ (15分 → 0分) + +### 注意事項 + +1. **mainブランチ**: 本番ブランチでは全チェックを実行することを推奨(path filtersを緩めに設定) +2. **skipped状態の処理**: Quality Gateで`skipped`を成功として扱う必要があります +3. **ワークフロー自体の変更**: `.github/workflows/**`は常に含める + ## Best Practices ### CI Workflow @@ -217,6 +295,7 @@ For npm registry instead of GitHub Packages: 2. **Fail early**: Put fastest checks first (lint before tests) 3. **Clear feedback**: Use descriptive job and step names 4. **Protect main**: Require CI to pass before merging +5. **Optimize with path filters**: Skip unnecessary jobs when files haven't changed ### Release Workflow diff --git a/.github/workflows/templates/unified-ci.yml b/.github/workflows/templates/unified-ci.yml index e89b439e..4a8e3b11 100644 --- a/.github/workflows/templates/unified-ci.yml +++ b/.github/workflows/templates/unified-ci.yml @@ -11,20 +11,51 @@ # - Automatic PR size labels (size/S, size/M, size/L, size/XL) # - Warning for XL PRs (>1000 lines or >30 files) # - Concurrency control to cancel outdated runs +# - Path filters for optimized execution (only run when relevant files change) # # Usage: # 1. Copy this file to .github/workflows/ci.yml # 2. Customize the quality check commands for your project # 3. Ensure your test runner generates coverage-summary.json # 4. Add size labels to your repository (size/S, size/M, size/L, size/XL) +# 5. Adjust the path filters to match your project structure name: CI on: pull_request: branches: [main, develop] + # Path filters: only run CI when code or configuration changes + # Customize these patterns to match your project structure + paths: + - '**.js' + - '**.ts' + - '**.jsx' + - '**.tsx' + - '**.json' + - 'package.json' + - 'package-lock.json' + - 'pnpm-lock.yaml' + - 'yarn.lock' + - '.github/workflows/**' + - 'src/**' + - 'test/**' + - 'tests/**' + # Add project-specific patterns below: + # - 'apps/**' + # - 'packages/**' push: branches: [main, develop] + # Less restrictive on main/develop to ensure all checks run + paths: + - '**.js' + - '**.ts' + - '**.jsx' + - '**.tsx' + - '**.json' + - 'package.json' + - 'package-lock.json' + - '.github/workflows/**' # Cancel in-progress runs for the same workflow on the same PR concurrency: