CircleCIで特定のファイルが変更された時のみstepを継続させたい時に使うcommand

CircleCIで特定の拡張子のファイル(今回は `.go`)のみ変更されていた場合にのみjobのstepsを継続させたい
ケースに対応させるためのcommand
動かすのに時間溶かしたのでメモとして残しておきます

Dynamic Configurationを利用した例は見かけましたが
https://qiita.com/hatai/items/76ade5b81a0b2076263c

以下の都合でjob自体は動かしたかったのと既存のCIにサクッと組み込みたかったのでcommandsで実装しました

- プロダクションコード変更されていないのにLintやtestを動かしたくない
- GitHubのbranch protectionの都合job自体はskip動作で動かしたい

## commands部分の実装

```yaml
  check_go_changes_in_pr_and_halt:
    description: "Uses GitHub REST API (curl+jq) to find PR target branch, fetches history, checks for *.go changes against base branch, and halts if none found. Requires checkout and GITHUB_TOKEN."
    parameters:
      github-token:
        type: env_var_name
        default: GITHUB_TOKEN
    steps:
      - run:
          name: Determine Target Branch via REST API, Fetch, Check Go Changes
          command: |
            # --- 1. Check Context and Required Env Vars ---
            if [ -z "$CIRCLE_PULL_REQUEST" ]; then
              echo "Not a Pull Request context. Proceeding."
              exit 0
            fi

            if [ -z "${<< parameters.github-token >>}" ]; then
              echo "Error: github-token parameters is not set. Required for GitHub REST API call."
              exit 1 # Configuration error, fail fast
            fi

            # --- 2. Parse PR Info ---
            PR_NUMBER=$(basename "$CIRCLE_PULL_REQUEST")
            if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
                echo "Error: Could not parse PR number from CIRCLE_PULL_REQUEST: $CIRCLE_PULL_REQUEST"
                exit 0 # Proceed safely
            fi

            # Use CircleCI env vars for owner/repo
            OWNER="$CIRCLE_PROJECT_USERNAME"
            REPO="$CIRCLE_PROJECT_REPONAME"
            if [ -z "$OWNER" ] || [ -z "$REPO" ]; then
                echo "Error: Could not determine repository owner/name from CircleCI env vars (CIRCLE_PROJECT_USERNAME, CIRCLE_PROJECT_REPONAME)."
                exit 0 # Proceed safely
            fi
            echo "Detected PR: $OWNER/$REPO #$PR_NUMBER"

            # --- 3. Call GitHub REST API determined Target Branch ---
            API_URL="https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER"
            echo "Querying GitHub REST API: $API_URL"

            # Use curl to get PR details, jq to extract base.ref
            # -sS: Silent but show errors. -f: Fail fast on HTTP errors (>=400). -L: Follow redirects.
            if ! HTTP_RESPONSE=$(curl -sSLf \
                                 -H "Authorization: Bearer ${<< parameters.github-token >>}" \
                                 -H "Accept: application/vnd.github.v3+json" \
                                 "$API_URL"); then
                echo "Error: Failed to fetch PR details from GitHub API."
                echo "Check API URL, token permissions (needs 'repo' scope for private repos), and network."
                # Don't echo potentially sensitive response here if curl failed entirely
                echo "Proceeding with job steps as a safety measure."
                exit 0 # Exit script successfully to proceed safely
            fi

            # Parse the JSON response to get the target branch name
            TARGET_BRANCH=$(echo "$HTTP_RESPONSE" | jq -r '.base.ref')

            # Check if jq successfully extracted a non-null, non-empty value
            if [ -z "$TARGET_BRANCH" ] || [ "$TARGET_BRANCH" == "null" ]; then
                echo "Error: Could not parse target branch (.base.ref) from API response."
                # Consider logging $HTTP_RESPONSE only if necessary for debugging, might contain sensitive info
                # echo "API Response: $HTTP_RESPONSE"
                echo "Proceeding with job steps as a safety measure."
                exit 0 # Exit script successfully to proceed safely
            fi
            echo "Successfully determined Target Branch via REST API: $TARGET_BRANCH"

            # --- 4. Perform git diff ---
            echo "Checking for *.go file changes against base branch."
            echo "git diff --name-only --no-prefix origin/$TARGET_BRANCH $CIRCLE_BRANCH"
            if ! git diff --name-only --no-prefix origin/$TARGET_BRANCH $CIRCLE_BRANCH > changed_files.list; then
               echo "Error: 'git diff \"origin/$TARGET_BRANCH\" \"$CIRCLE_BRANCH\"' command failed (Base branch likely not found)."
               rm -f changed_files.list
               exit 0 # Proceed safely
            fi

            # --- 5. Check diff output ---
            echo "Checking for *.go file changes in diff output..."
            if ! grep -q '\.go$' changed_files.list; then
              echo "No *.go files changed. Halting subsequent steps."
              rm -f changed_files.list
              circleci-agent step halt
            else
              echo "*.go files changed. Proceeding."
              rm -f changed_files.list
              exit 0
            fi
```

## 呼び出し部分のstepsの実装

```yaml
jobs:
  build_and_test:
    docker:
      - image: cimg/go:1.24.2 # Or any other image where you can install curl/jq
    steps:
      - checkout

      # Call the command
      - check_go_changes_in_pr_and_halt

      # Subsequent steps (only run if halt was not called)
      - run:
          name: Build Go Project (if changes detected)
          # ...
      - run:
          name: Test Go Project (if changes detected)
          # ...
```