TypeScript(Next.js)のCIをGitHub Actionsで構築してみる！

# はじめに
こんにちは、H×Hのセンリツ大好きエンジニアです。

自分で新規プロダクトを開発してみよう！となった際に、「CIを最初に作っておくと便利だから作ってみるか〜」という軽い気持ちで手を出してみました。
（思ったより苦戦しました。。。）

なので、同じ思いを抱いている方に少しでも楽をして欲しいので、記事に書きました！
フロントエンド(TypeScript)とバックエンド(Golang)のCIを作成したのですが、今回はフロントエンドについてです。バックエンドも記事を書いたら載せます。

# GitHub Actionsとは？
こちらの記事が非常に分かりやすいので、詳しく知りたい方は参考にしてみて下さい！（他力本願寺）
[【初心者向け】【入門】GitHub Actionsの書き方についてデバッグ設定、runs-onやcheckoutなどの仕組みや構造も含めて徹底解説](https://qiita.com/shun198/items/14cdba2d8e58ab96cf95)

簡単に言うと、GitHub上でCI/CDが構築できるよ！と言うものです。
（今までは、CircleCIなどの外部サービスと連携させる必要がありましたが、必要なくなります。一つのサービスで完結できるのって素晴らしいですね！）

# CIの内容
TypeScriptのCIでは、主に２点を行います。
1. ESLintを用いた静的解析
1. Dockleを用いた脆弱性検査

フロントエンドは、テストはしなくても良いかな（というか分からない。。。）ので今回は省きました。

# 実装
Github Actionsはリポジトリ直下の`.github/workflows`にあるymlファイルに記述されたワークフローを元に実行されます。
公式リファレンス：　[GitHub Actions](https://github.co.jp/features/actions)

なので、まずはリポジトリ直下に`.github/workflows`ディレクトリを作成し、その中に今回使用するymlファイルを作成します。（名前はなんでも良いので、`frontend_ci.yml`とかにしておきます。）

構成図は以下になります。
```
.
├── .github
│   └── workflows
│       └── frontend_ci.yml <- コレ！
└── frontend                <- 中身はnpx create-next-appで生成される者たち
    ├── .eslintrc.json
    ├── .gitignore
    ├── .next
    ├── Dockerfile
    ├── README.md
    ├── next-env.d.ts
    ├── next.config.js
    ├── node_modules
    ├── package-lock.json
    ├── package.json
    ├── public
    ├── src
    └── tsconfig.json
.
.
.
```
「自分はこのリポジトリではフロントエンドだけ開発するからリポジトリ直下に置くよ！」という方はこの後のymlファイルの作業ディレクトリを`.`に変更して下さい。

そして、気になるymlファイルの中身ですが、以下のようにしました。
``` frontend_ci.yml
# ワークフローの名前
name: frontend_ci

# トリガーになる Github イベント
on:
  pull_request:
    branches:
      - develop
    # ルート直下の場合は不要
    paths:
      - "frontend/**"
  # 無くても良い（手動実行したい人だけ残す）
  workflow_dispatch:

# 環境変数を設定
env:
  DOCKER_IMAGE_TAG: "frontend"

# デフォルトの作業ディレクトリ変更
# ルート直下の場合は不要
defaults:
  run:
    working-directory: frontend

# ジョブ定義
jobs:
  # ジョブ名
  frontend_lint_check:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        # 使用するNode.jsのバージョンを定義
        node-version: [20.10.0]
    # GitHub Actionsの権限設定
    permissions:
      contents: read
      pull-requests: write
    steps:
      # リポジトリからソースコードの取得
      - name: Checkout Repository
        uses: actions/checkout@v4

      # Node.jsのセットアップ
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          # 利用したいNode.jsバージョンを指定
          node-version: ${{ matrix.node-version }}
          # npmのキャッシュを使用する設定
          cache: "npm"
          # ルート直下の場合は「./package-lock.json」に変更
          cache-dependency-path: ./frontend/package-lock.json

      # プロジェクトの依存関係をインストール
      - name: Install Dependencies
        run: npm ci

      # ReviewdogをESLintで実行
      - name: Run ESLint Reviewdog
        uses: reviewdog/action-eslint@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          reporter: github-pr-review
          eslint_flags: "src/**/*.{ts,tsx}"
          # ルート直下の場合は不要
          workdir: frontend/

  frontend_build_check:
    runs-on: ubuntu-latest
    steps:
      # リポジトリからソースコードの取得
      - name: Checkout Repository
        uses: actions/checkout@v4

      # Dockerのコンテナイメージをビルド
      - name: Docker Image Build
        run: |
          docker build -f Dockerfile -t ${{ env.DOCKER_IMAGE_TAG }} .

      # Dockleで脆弱性検査を行う
      - name: Run Dockle
        uses: erzz/dockle-action@v1
        with:
          image: ${{ env.DOCKER_IMAGE_TAG }}
          exit-code: 1
          failure-threshold: fatal
```

ほとんどコメントアウトで記述しているので、割愛しながら説明していきます。
``` frontend_ci.yml
# トリガーになる Github イベント
on:
  pull_request:
    branches:
      - develop
    # ルート直下の場合は不要
    paths:
      - "frontend/**"
  # 無くても良い（手動実行したい人だけ残す）
  workflow_dispatch:
```
ここでは、GitHub Actionsを呼び出すトリガーを設定します。
今回は、以下のように設定しました。
- developブランチにPull Requestが作成された時に発火
    - pathsでfrontendディレクトリの中身が編集されていれば実行するように変更
- 手動実行もしたいので`workflow_dispatch`をトリガーに追加

```
# 環境変数を設定
env:
  DOCKER_IMAGE_TAG: "frontend"
```
今回は、Dockerを用いてフロントエンドとバックエンドを管理しており、脆弱性検査の時にDockerfileのイメージを指定するので、ここで環境変数に設定しています。
## 静的解析
``` frontend.yml
# ジョブ定義
jobs:
  # ジョブ名
  frontend_lint_check:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        # 使用するNode.jsのバージョンを定義
        node-version: [20.10.0]
    # GitHub Actionsの権限設定
    permissions:
      contents: read
      pull-requests: write
```
`jobs`では、実行するジョブを定義します。
そして、実行する内容を記述する前に必要な設定を記述します。
- ジョブの実行を行うOSの指定
- Node.jsバージョンの指定（変数に入れることで管理しやすくする）
- GitHub Actionsの権限設定

最後の権限設定に関してですが、これはESLintで指摘項目があった際にPull Requestのコメントに内容を送信してもらうためです。（直す箇所が特定できるのでめためた便利です）
``` frontend_ci.yml
steps:
      # リポジトリからソースコードの取得
      - name: Checkout Repository
        uses: actions/checkout@v4

      # Node.jsのセットアップ
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          # 利用したいNode.jsバージョンを指定
          node-version: ${{ matrix.node-version }}
          # npmのキャッシュを使用する設定
          cache: "npm"
          # ルート直下の場合は「./package-lock.json」に変更
          cache-dependency-path: ./frontend/package-lock.json
```
ここでジョブの内容を記述します。
まずはソースコードを取得し、Node.jsのセットアップを行います。

今回はセットアップ時にnpmのキャッシュを使いたいので、`package-lock.json`の場所を明記してあげます。
`yarn`や`pnpm`を使用している方はwithの中身を変更して下さい。
``` frontend_ci.yml
      # ReviewdogをESLintで実行
      - name: Run ESLint Reviewdog
        uses: reviewdog/action-eslint@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          reporter: github-pr-review
          eslint_flags: "src/**/*.{ts,tsx}"
          # ルート直下の場合は不要
          workdir: frontend/
```
reviewdogという便利ツールを使います。
このツールは、各種linterの検出結果をレビューコメントとして自動的に投稿してくれるツールです。
公式リファレンス：[reviewdog/reviewdog](https://github.com/reviewdog/reviewdog)

今回はこのreviewdogのESLintバージョンで実行します。
オプションでは、以下のことを行なっています。
- `github_token`でreviewdogに権限を付与
- `reporter`で投稿方法を設定
    - Pull Requestのレビューコメントに設定
- `eslint_flags`で静的解析するファイルを選択
    - srcフォルダ内にある全てのTypeSctiptファイルを対象
- `workdir`で作業ディレクトリ変更

このジョブを作成後、規則違反したソースコードをPull Requestにあげるとどうなるのか見てみましょう。
![スクリーンショット 2024-01-13 12.56.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653241/9fe57920-e2e2-9f07-9cfe-a85b5ec37394.png)

ちゃんと怒ってくれますね！（社会人になると怒ってくれる人が少なくなるのでbotに怒ってもらいましょう！）

## 脆弱性検査
次に、脆弱性検査のジョブです。
``` frontend_ci.yml
frontend_build_check:
    runs-on: ubuntu-latest
    steps:
      # リポジトリからソースコードの取得
      - name: Checkout Repository
        uses: actions/checkout@v4

      # Dockerのコンテナイメージをビルド
      - name: Docker Image Build
        run: |
          docker build -f Dockerfile -t ${{ env.DOCKER_IMAGE_TAG }} .

      # Dockleで脆弱性検査を行う
      - name: Run Dockle
        uses: erzz/dockle-action@v1
        with:
          image: ${{ env.DOCKER_IMAGE_TAG }}
          exit-code: 1
          failure-threshold: fatal
```
先ほど同様リポジトリからソースコードを取得した後、Dockerイメージをビルドしていきます。
自分はDockerを使っているのですが、Dockerを使っていない場合は分かりません。。。

最後に、脆弱性検査ツールのDockleを実行します。
今回は、指摘される警告レベルをfatalに設定しました。

実際に動かしてみましょう。
出力結果はbackendのものですが、実装は同じなので見てみます！
![スクリーンショット 2024-01-13 13.18.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653241/dd8976ac-6e12-bab8-1363-f2311f1982a9.png)

こちらも、きちんと怒ってくれました。（感動）

# さいごに
CIをGitHub Actionsで構築してみました！（完全に理解した）

ただ、自分より完全理解した方がいらっしゃれば「もっとこうするといいよ！」などのアドバイスを頂けると幸いです。

ここまでご覧いただきありがとうございました！