【GitHub Actions】ReactプロジェクトのCI/CDパイプラインを構築してみた

GitHub Actions学習のまとめとして、Create React AppのプロジェクトのCI/CDパイプラインを構築してみました。

https://qiita.com/suzuki0430/items/951ed9753c04743537cc

https://qiita.com/suzuki0430/items/d625f8b57ae317ae7d66

使用したnpmパッケージについては以下の記事でまとめています。

https://qiita.com/suzuki0430/items/3866e48e8fc4754aad41


#プロジェクトの作成
`npx create-react-app react-app --use-npm`でプロジェクトを作成します。

#ワークフローの構成
Git-flowでの運用を想定して、以下のタイミングで実行するワークフローを作成します。

1. featureからdevelop向けのプルリクを作成したとき
2. プルリクが承認されてfeatureがdevelopへマージされたとき
3. developからmaster向けのプルリクを作成したとき
4. プルリクが承認されてdevelopがmasterへマージされたとき

![スクリーンショット 2021-04-02 22.05.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/a9e5b81e-60fc-db8b-44eb-8b3f2035cb5a.png)

![スクリーンショット 2021-04-02 22.12.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/ce6e5358-41a4-3d68-8897-b741f76e5565.png)

加えて、リリースされたらSlackに通知するワークフローも作成します。

#ワークフローの作成
前項の図でワークフローが4つありましたが、これらを1つのymlファイルに記述します。

##ワークフロー①の作成(一部分)
まずは①(featureからdevelop向けのプルリクを作成したとき)のワークフローの内容を記述します。

```ci.yml
name: CI
on:
  pull_request:
    branches: [develop] #develop向けのプルリクが作成されたときにジョブを実行

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2 #チェックアウトするアクション
      - name: Use NodeJS
        uses: actions/setup-node@v1 #特定バージョンのnodeを設定するアクション
        with:
          node-version: "12.x"
      - run: npm ci #dependencyのインストール
      - run: npm run format:check #コードフォーマットチェック
      - run: npm test -- --coverage #テスト実行
        env:
          CI: true #現在の環境がCI環境であることを仮定
```

ここではdependencyのインストール、コードフォーマットチェック、テスト実行を記述しています。
CIにおいては、`npm install`ではなく`npm ci`でdependencyのインストールを行います。
`npm ci`では、依存関係の更新をせずに整合性チェックと依存パッケージのダウンロードのみを行うため、`npm install`より高速に動作し、CIで必要なことだけを行うことができます。

https://qiita.com/mstssk/items/8759c71f328cab802670

`CI: true`ではテストがCI環境で実行されることを仮定しています。

https://jestjs.io/ja/docs/cli#--ci

##ワークフロー②の作成
developからworkflowというfeatureブランチをきって、`git push`してdevelop向けのプルリクをつくるとワークフローが実行されます。
![スクリーンショット 2021-03-28 9.49.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/eadd9c39-3222-f0c0-39ad-906c364f903a.png)

ワークフロー②(プルリクが承認されてfeatureがdevelopへマージされたとき)の内容のステップを追記します。

```ci.yml
name: CI
on:
  pull_request:
    branches: [develop]
  push:
    branches: [develop] #developにマージされたときに実行

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use NodeJS
        uses: actions/setup-node@v1
        with:
          node-version: "12.x"
      - run: npm ci
      - run: npm run format:check
      - run: npm test -- --coverage
        env:
          CI: true
      - name: Build Project #ステージング用のビルドを行うステップ
        if: github.event_name == 'push' #pushイベントのときだけ実行
        run: npm run build #ビルド実行
      - run: npm install -g surge #surgeのインストール
      - name: Deploy to Staging #ステージングデプロイを行うステップ
        if: github.event_name == 'push' #pushイベントのときだけ実行
        run: npx surge --project ./build --domain awesome-quiet.surge.sh #デプロイ
        env: #surgeの認証
          SURGE_LOGIN: ${{ secrets.SURGE_LOGIN }} #surge whoamiで取得
          SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }} #surge tokenで取得
```

developへのマージをトリガにするために、develop向けのpushイベントを追加します。
また、ワークフローは①と②で共有されているので、ビルドやデプロイがpull_requestイベントで実行されないように、`if: github.event_name == 'push'`という実行条件を追加します。

今回、デプロイにはsurgeというnpmパッケージを使用しています。ログイン名とパスワード(token)を予め作成して、GitHubリポジトリのsecretsに環境変数として登録しておきます。

ワークフローの実行内容を確認すると、developへのプルリク時点ではbuild, deployステップを飛ばしています。
![スクリーンショット 2021-03-28 10.33.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/2ee01398-a854-0d4a-ba3d-9be47b80d39e.png)

developへマージ(push)するとbuild, deployが実行されています。
![スクリーンショット 2021-03-28 10.49.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/f3b42a26-28ba-ca91-63a7-d771dbe624c9.png)

##dependencyのキャッシュ
ワークフローごとに`npm ci`行うのは無駄なので、dependenciesのキャッシュ用のアクションをステップに追加します。

https://github.com/actions/cache

```yml
    steps:
      - uses: actions/checkout@v2
      - name: Cache node_modules
        uses: actions/cache@v1
        with:
          path: ~/.npm #キャッシュしたファイルとキーを格納する場所(OSによって異なる)
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} #キャッシュのリストアや保存をするためのキー
          restore-keys: | #キャッシュをリストアするためのキーのリスト
            ${{ runner.os }}-node-
```
`hashFiles`を使用することで、depencencyに変更があったときに新しいキャッシュをつくることができます。

最初のワークフローを実行すると、以下のようにキーが生成されます。
![スクリーンショット 2021-03-28 21.11.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/908699a3-6822-6a63-739a-c5becba93b4d.png)

##Artifact(テストカバレッジ、ビルドファイル)のアップロード
テストとビルドの後にGitHubのActionsタブからArtifactをダウンロードできるようにするため、これらをアップロードするアクションをステップを追加します。

https://github.com/actions/upload-artifact

```ci.yml
      - run: npm test -- --coverage
        env:
          CI: true
      - name: Upload Test Coverage
        uses: actions/upload-artifact@v1 #artifactをアップロードするアクション
        with:
          name: code-coverage #ダウンロード時の表示名
          path: coverage #アップロードするフォルダのパス
      - name: Build Project
        if: github.event_name == 'push'
        run: npm run build
      - name: Upload Build Coverage
        if: github.event_name == 'push'
        uses: actions/upload-artifact@v1
        with:
          name: build
          path: build
```

ワークフローを実行すると以下のようにArtifactがアップロードされます。
![スクリーンショット 2021-03-28 21.55.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/0331d781-86b0-e1b2-379f-ff562dd0a841.png)

##masterにマージされたときだけ実行するステップ
ワークフロー④(プルリクが承認されてdevelopがmasterへマージされたとき)のときだけ実行されるステップを作成します。

###リリースの作成
masterにマージされたときにリリースを作成するステップを追加します。
リリースの作成には<a href="https://qiita.com/suzuki0430/items/3866e48e8fc4754aad41#semantic-release%E3%81%AE%E5%B0%8E%E5%85%A5">semantic-release</a>を使います。

```ci.yml
      - name: Create a Release
        if: github.event_name == 'push' && github.ref == 'refs/heads/master'
        run: npx semanic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

また、リリースの画面からbuild.zip, coverage.zipをダウンロードできるようにします。


```ci.yml
      - name: ZIP Assets
        if: github.event_name == 'push' && github.ref == 'refs/heads/master'
        run: |
          zip -r build.zip ./build
          zip -r coverage.zip ./coverage
```
リリースの設定は以下のようになります。

```release.config.js
module.exports = {
  branches: "master",
  repositoryUrl: "https://github.com/suzuki0430/react-app",
  plugins: [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    [
      "@semantic-release/github",
      {
        assets: [
          {path: "build.zip", label: "Build"},
          {path: "coverage.zip", label: "Coverage"},
        ],
      },
    ],
  ],
};

```

###codecovへのテストカバレッジのアップロード
codecovはコードのカバレッジを計測し、可視化したりslackに通知したりできる外部サービスです。
envでcodecovで作成したTOKENを指定するだけで、カバレッジレポートをアップロードすることができます。

https://codecov.io/gh

```ci.yml
      - name: Upload Coverage Reports
        if: github.event_name == 'push' && github.ref == 'refs/heads/master'
        run: npx code-coverage
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
```

##Slack通知用のワークフローの作成
リリースが作成されたときにSlack通知を行うワークフローを作成します。
`ci.yml`のCreate releaseステップをトリガにするため、CUSTOM_TOKENを新しくつくります。

![スクリーンショット 2021-03-29 21.27.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/6e80e959-661c-d97d-06b8-c02783d927b2.png)

リリースの作成(published)をトリガにしたワークフローを以下のように作成します。

```release.yml
name: Notify on Release
on:
  release:
    types: [published]

jobs:
  slack-message:
    runs-on: ubuntu-latest
    steps:
      - name: Slack Message
        run: |
          curl -X POST -H 'Content-type: application/json' --data '{"text":"New release ${{ github.event.release.tag_name }} is out, <${{ github.event.release.html_url }}|check it out now.>"}' ${{ secrets.SLACK_WEBHOOK }}
```

#おわりに
GitHub Actionsの使い方が大体わかったので、今度は社内業務で役に立つようなワークフローを作成してみたいと思います。

#参考資料

https://www.udemy.com/share/102DqFCEEZd11VRno=/