diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 00000000..a117c8ce --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,20 @@ +self-hosted-runner: + # Labels of self-hosted runner in array of strings. + labels: [] + +# Configuration variables in array of strings defined in your repository or +# organization. `null` means disabling configuration variables check. +# Empty array means no configuration variable is allowed. +config-variables: null + +# Configuration for file paths. The keys are glob patterns to match to file +# paths relative to the repository root. The values are the configurations for +# the file paths. Note that the path separator is always '/'. +# The following configurations are available. +# +# "ignore" is an array of regular expression patterns. Matched error messages +# are ignored. This is similar to the "-ignore" command line option. +paths: + .github/workflows/**/*.yml: + ignore: + - 'shellcheck reported issue in this script: SC[0-9]+:(info|style)' diff --git a/.github/labels.yml b/.github/labels.yml index 1eff5e5e..817c4b15 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -117,6 +117,10 @@ description: 'リリース済み' # ── Size ────────────────────────────────────────── +- name: 'size/XS' + color: 'c2e0c6' + description: 'PR サイズ Extra Small' + - name: 'size/S' color: '2cbe4e' description: 'PR サイズ Small' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a2e8fb5..c49b6b40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,9 +9,12 @@ on: - '**.mjs' - '**.cjs' - '**.json' + - '**.yml' + - '**.yaml' - '**.sh' - '**.bats' - '.github/workflows/**' + - '.github/actionlint.yaml' - 'package.json' - 'package-lock.json' - '.eslintrc.*' @@ -25,9 +28,12 @@ on: - '**.mjs' - '**.cjs' - '**.json' + - '**.yml' + - '**.yaml' - '**.sh' - '**.bats' - '.github/workflows/**' + - '.github/actionlint.yaml' - 'package.json' - 'package-lock.json' - '.eslintrc.*' @@ -75,6 +81,7 @@ jobs: - 'test/integration/**' workflows: - '.github/workflows/**' + - '.github/actionlint.yaml' dependencies: - 'package.json' - 'package-lock.json' diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index edc7a60e..51276aba 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -81,7 +81,7 @@ jobs: echo "Version to release: $(cat $GITHUB_OUTPUT | grep version)" - name: Fix workspace permissions - run: sudo chown -R $(id -u):$(id -g) "$GITHUB_WORKSPACE" + run: sudo chown -R "$(id -u):$(id -g)" "$GITHUB_WORKSPACE" - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4 diff --git a/.github/workflows/quality-gate-fallback.yml b/.github/workflows/quality-gate-fallback.yml index f1e6b45b..261e2b77 100644 --- a/.github/workflows/quality-gate-fallback.yml +++ b/.github/workflows/quality-gate-fallback.yml @@ -1,8 +1,27 @@ # Quality Gate Fallback # -# CI ワークフローが paths フィルタでスキップされた場合に -# Required Status Check "Quality Gate" を Pass で報告する。 -# CI が実行された場合は ci.yml 側の Quality Gate が優先される。 +# Required Status Check "Quality Gate" を必ず報告するための fallback ワークフロー。 +# +# 必要な背景: +# - setup-team-protection.sh はブランチ保護に "Quality Gate" を必須チェックとして登録する +# - ci.yml の Quality Gate ジョブが paths フィルタでスキップされた場合や、 +# ci.yml 自体が存在しない / 走らない場合、PR は永遠に +# "Expected — Waiting for status to be reported" のまま blocked になる +# - この fallback は ci.yml の実行可否をチェックし、走っていなければ Pass を emit する +# +# 動作: +# - ci.yml が走っている / 成功している場合 → 何もしない (本体の Quality Gate が優先) +# - ci.yml が走っていない場合 → Pass で Quality Gate を emit +# +# 使い方: +# .github/workflows/quality-gate-fallback.yml にコピーして配置 +# setup-team-protection.sh で "Quality Gate" を必須チェックに設定する場合は必須 +# +# 既知の制限: +# - GITHUB_TOKEN で push されたコミット (claude[bot] / dependabot[bot] が +# GITHUB_TOKEN で押した場合など) では `pull_request` イベントが発火しないため、 +# この fallback も実行されない。その場合は別途 PAT 経由の push か +# workflow_dispatch / repository_dispatch での再トリガーが必要。 # name: CI Fallback @@ -52,10 +71,14 @@ jobs: core.setOutput('ci_running', 'false'); } } catch (err) { + // 権限不足 (actions: read 未付与) や一時的な API 障害でクエリ失敗時は + // 安全側に倒す。"CI 実行中扱い" にして fallback の Pass emit をスキップし、 + // 本体の Quality Gate (ci.yml) の結果を待つ。Required check が + // 未報告のままになる可能性はあるが、誤った Pass / Fail よりは安全。 core.warning(`Could not query workflow runs (${err.status || err.message}). Skipping fallback.`); core.setOutput('ci_running', 'true'); } - name: Pass (CI skipped) if: steps.check.outputs.ci_running == 'false' - run: echo "Quality Gate passed (CI skipped — no code changes detected)." + run: echo "Quality Gate passed (CI skipped — no code changes detected or workflow disabled)." diff --git a/.github/workflows/templates/monorepo-release.yml b/.github/workflows/templates/monorepo-release.yml index 7ee00014..85e615e6 100644 --- a/.github/workflows/templates/monorepo-release.yml +++ b/.github/workflows/templates/monorepo-release.yml @@ -55,7 +55,7 @@ jobs: id: changes run: | # Get the previous commit - if [ $(git rev-list --count HEAD) -eq 1 ]; then + if [ "$(git rev-list --count HEAD)" -eq 1 ]; then # First commit, consider all packages changed echo "package1_changed=true" >> $GITHUB_OUTPUT echo "package2_changed=true" >> $GITHUB_OUTPUT diff --git a/.github/workflows/update-dev-tools.yml b/.github/workflows/update-dev-tools.yml index 8e76bbfa..8e916008 100644 --- a/.github/workflows/update-dev-tools.yml +++ b/.github/workflows/update-dev-tools.yml @@ -70,7 +70,6 @@ jobs: - name: Compare versions id: compare run: | - UPDATES="" HAS_UPDATES="false" compare_version() { diff --git a/templates/github/labels.yml b/templates/github/labels.yml index 0bb88591..817c4b15 100644 --- a/templates/github/labels.yml +++ b/templates/github/labels.yml @@ -94,6 +94,14 @@ color: 'bfdadc' description: '自動検出された機能・改善' +- name: 'npm' + color: 'cb3837' + description: 'npm/yarn 依存関係の更新' + +- name: 'docker' + color: '0db7ed' + description: 'Docker 依存関係の更新' + # ── CI/CD ───────────────────────────────────────── - name: 'ci' color: '006b75' @@ -102,3 +110,29 @@ - name: 'infrastructure' color: '5319e7' description: 'インフラ関連' + +# ── Release ─────────────────────────────────────── +- name: 'released' + color: 'ededed' + description: 'リリース済み' + +# ── Size ────────────────────────────────────────── +- name: 'size/XS' + color: 'c2e0c6' + description: 'PR サイズ Extra Small' + +- name: 'size/S' + color: '2cbe4e' + description: 'PR サイズ Small' + +- name: 'size/M' + color: 'fbca04' + description: 'PR サイズ Medium' + +- name: 'size/L' + color: 'e99695' + description: 'PR サイズ Large' + +- name: 'size/XL' + color: 'd93f0b' + description: 'PR サイズ XLarge' diff --git a/templates/workflows/claude.yml b/templates/workflows/claude.yml index 68544915..22a4fefc 100644 --- a/templates/workflows/claude.yml +++ b/templates/workflows/claude.yml @@ -2,8 +2,7 @@ # # Issue や PR で @claude をメンションすると Claude Code が応答する。 # Bot からのイベントは自動除外。 -# PR イベント(pull_request_review_comment / pull_request_review)では Draft PR も除外。 -# issue_comment イベントでは Draft 判定不可のため除外なし。 +# PR イベントと PR 上の issue_comment では Draft PR も除外。 # # 使い方: # .github/workflows/claude.yml にコピーして配置 @@ -55,7 +54,8 @@ jobs: github.event.issue.author_association ) && ( - (github.event_name == 'issue_comment' && contains(github.event.comment.body || '', '@claude')) || + (github.event_name == 'issue_comment' && contains(github.event.comment.body || '', '@claude') && + !(github.event.issue.pull_request && github.event.issue.pull_request.url && github.event.issue.draft == true)) || (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') && @@ -69,26 +69,28 @@ jobs: pull-requests: write issues: write id-token: write - actions: read + actions: read # Required for Claude to read CI results on PRs checks: read steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 1 - name: Run Claude Code id: claude - uses: anthropics/claude-code-action@6e2bd52842c65e914eba5c8badd17560bd26b5de # v1.0.89 + uses: anthropics/claude-code-action@d5726de019ec4498aa667642bc3a80fca83aa102 # v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + # This is an optional setting that allows Claude to read CI results on PRs additional_permissions: | actions: read checks: read + # System prompt to automatically create PRs when working on Issues claude_args: | - --allowedTools "Bash(git:*),Bash(npm:*),Bash(pnpm:*),Bash(npx:*)" + --allowedTools "Bash(git:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh pr status:*),Bash(npm:*),Bash(pnpm:*),Bash(npx:*)" --system-prompt "When working on GitHub Issues (not PRs), after completing all code changes, commit and push the changes to the Claude branch. Do not run 'gh pr create'; this workflow creates the pull request from the branch after Claude finishes." - name: Create pull request from Claude branch diff --git a/templates/workflows/dependabot-auto-merge.yml b/templates/workflows/dependabot-auto-merge.yml index ddb1154f..2f4bbf4d 100644 --- a/templates/workflows/dependabot-auto-merge.yml +++ b/templates/workflows/dependabot-auto-merge.yml @@ -35,7 +35,7 @@ jobs: # 更新種別を取得(patch / minor / major) - name: Fetch Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@ffa630c65fa7e0ecfa0625b5ceda64399aea1b36 # v3 + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -44,7 +44,7 @@ jobs: # gh pr merge --auto が enablePullRequestAutoMerge エラーで落ちるため、 # 既知エラーだけ自動承認へフォールバックしてワークフロー自体は green を維持する。 - name: Auto-merge patch updates - if: steps.metadata.outputs.update-type == 'version-update:semver-patch' + if: github.actor == 'dependabot[bot]' && steps.metadata.outputs.update-type == 'version-update:semver-patch' run: | set +e MERGE_OUTPUT=$(gh pr merge "$PR_URL" --auto --merge 2>&1) @@ -69,7 +69,7 @@ jobs: # GITHUB_TOKEN は既定で PR 承認不可のため continue-on-error: true で失敗しても CI をブロックしない # リポジトリ設定「Allow GitHub Actions to create and approve pull requests」を有効にすると承認も動作する - name: Label minor updates - if: steps.metadata.outputs.update-type == 'version-update:semver-minor' + if: github.actor == 'dependabot[bot]' && steps.metadata.outputs.update-type == 'version-update:semver-minor' run: | gh label create "dependabot-minor" --color "0366d6" --description "Minor dependency update" --force gh pr edit "$PR_URL" --add-label "dependabot-minor" @@ -78,7 +78,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Auto-approve minor updates - if: steps.metadata.outputs.update-type == 'version-update:semver-minor' + if: github.actor == 'dependabot[bot]' && steps.metadata.outputs.update-type == 'version-update:semver-minor' continue-on-error: true run: | gh pr review "$PR_URL" --approve --body "Auto-approved: minor version update" @@ -88,7 +88,7 @@ jobs: # major: ラベル付与してレビュー必須 - name: Label major updates for review - if: steps.metadata.outputs.update-type == 'version-update:semver-major' + if: github.actor == 'dependabot[bot]' && steps.metadata.outputs.update-type == 'version-update:semver-major' run: | gh label create "needs-review" --color "fbca04" --description "レビュー待ち" --force gh label create "breaking-change" --color "b60205" --description "破壊的変更あり" --force diff --git a/templates/workflows/label-sync.yml b/templates/workflows/label-sync.yml index c7f47298..892029a1 100644 --- a/templates/workflows/label-sync.yml +++ b/templates/workflows/label-sync.yml @@ -31,7 +31,7 @@ jobs: timeout-minutes: 5 steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - name: Sync labels uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2 diff --git a/templates/workflows/scheduled-maintenance.yml b/templates/workflows/scheduled-maintenance.yml index 43411052..93e1d964 100644 --- a/templates/workflows/scheduled-maintenance.yml +++ b/templates/workflows/scheduled-maintenance.yml @@ -44,7 +44,7 @@ jobs: fi - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 1 token: ${{ secrets.CLAUDE_PR_GITHUB_TOKEN }} @@ -54,7 +54,7 @@ jobs: - name: Run scheduled maintenance id: maintenance - uses: anthropics/claude-code-action@b47fd721da662d48c5680e154ad16a73ed74d2e0 # v1 + uses: anthropics/claude-code-action@d5726de019ec4498aa667642bc3a80fca83aa102 # v1 env: CLAUDE_BRANCH: ${{ env.CLAUDE_BRANCH }} with: diff --git a/test/integration/workflows.bats b/test/integration/workflows.bats index d01ee0e8..94df1076 100644 --- a/test/integration/workflows.bats +++ b/test/integration/workflows.bats @@ -29,6 +29,16 @@ load ../test_helper/test_helper grep -q "npm run test:coverage" "$workflow" } +@test "CI workflow runs workflow lint when actionlint config changes" { + local workflow="${REPO_ROOT}/.github/workflows/ci.yml" + assert_file_exists "$workflow" + + grep -Fq "'**.yml'" "$workflow" + grep -Fq "'**.yaml'" "$workflow" + grep -Fq "'.github/actionlint.yaml'" "$workflow" + grep -Fq "workflows:" "$workflow" +} + @test "CI workflow uses secure practices" { local workflow="${REPO_ROOT}/.github/workflows/ci.yml" diff --git a/test/template-workflows.test.js b/test/template-workflows.test.js index e3dbe847..8f8131e9 100644 --- a/test/template-workflows.test.js +++ b/test/template-workflows.test.js @@ -317,5 +317,15 @@ describe('Template workflow contracts', () => { expect(labels).toContain("name: 'breaking-change'"); }, ); + + test.each(['templates/github/labels.yml', '.github/labels.yml'])( + '%s: should define all labels used by PR size check', + (labelsPath) => { + const labels = readWorkflow(labelsPath); + for (const size of ['XS', 'S', 'M', 'L', 'XL']) { + expect(labels).toContain(`name: 'size/${size}'`); + } + }, + ); }); });