GithubActionsで環境変数によってルートディレクトリを切り替えてVercelにデプロイしてみる[Vercel,GithubActions]

個人で関わっているプロジェクトで、モノレポでフロントエンドアプリケーションを2つ管理(ver2とver3)しており、vercelで登録しているネームサーバーを使い回しながら最新版のアプリケーションをstagingにvercelにデプロイする必要がありました。

しかし、vercelのGithub連携ではルートディレクトリが1つしか設定できないのに加えて、環境変数ごとにルートディレクトリを切り替えることができないため、CDを回すことが困難でした。

今回はその問題をGithubActionsで解決してみました。

# Actionsを組む
## トークンを作成する

Vercelで使用するためのアクセストークンを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3766230/700c6802-7288-4c3e-b848-4c150d23c71e.png)

作成したらGithubのリポジトリにsecretsを以下のように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3766230/5230c718-4460-44fb-a7e7-26ff7152ffa0.png)


## ID等を調べる

トークン以外にも2つシークレットに設定する必要があるのでそちらを調査します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3766230/52713307-25f4-44f3-adb9-6c5a6da9d752.png)

設定する内容は以下のようになっています。

|名前|概要
|---|---
|PROJECT_ID|プロジェクトのsettingsにあるID
|ORG_ID|TeamのsettingsにあるTeamIDのこと

## workflowを組んでみる

今回vercelにデプロイするにあたって、ドメインは無料で準備できましたが、vercelで使える環境変数はHobbyプランだと準備できなかったので、ドメインでstgがprdかを区別しようと思います。

|環境|ドメイン
|---|---
|production|monorepo-deploy-vercel.vercel.app
|staging|stg-monorepo-deploy-vercel.vercel.app

以下workflowです。
- prdリリース用
```yml
name: deploy
env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./yarn
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '20'
      - name: Install dependencies
        run: yarn install
      - name: install vercel CLI
        run: yarn global add vercel
      - name: Pull Vercel Environment Information
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
      - name: Build Project Artifacts
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
      - name: Deploy to Vercel (production)
        id: vercel_deploy
        env:
            VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
        run: |
              output=$(vercel --token $VERCEL_TOKEN)
              echo "$output"
              echo "url=$output" >> $GITHUB_OUTPUT
      - name: set Vercel Alias
        run: |
            vercel alias set ${{ steps.vercel_deploy.outputs.url }} monorepo-deploy-vercel.vercel.app --token=${{ secrets.VERCEL_TOKEN }}
```

- stgリリース用
```yaml
name: deploy-stg.yml
env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
on:
  push:
    branches:
      - develop

jobs:
  deploy:
    runs-on: ubuntu-latest
    defaults:
        run:
          working-directory: ./pnpm
    steps:
        - name: Checkout code
          uses: actions/checkout@v2
        - name: Set up Node.js
          uses: actions/setup-node@v2
          with:
            node-version: '20'
        - uses: pnpm/action-setup@v4
          name: Install pnpm
          with:
            version: 10
            run_install: false
        - name: Install vercel CLI
          run: pnpm install -g vercel
        - name: Pull Vercel Environment Information
          run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
        - name: Build Project Artifacts
          run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
        - name: Deploy to Vercel (preview)
          id: vercel_deploy
          env:
            VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
          run: |
            output=$(vercel --token $VERCEL_TOKEN)
            echo "$output"
            echo "url=$output" >> $GITHUB_OUTPUT
        - name: set Vercel Alias
          run: |
            vercel alias set ${{ steps.vercel_deploy.outputs.url }} stg-monorepo-deploy-vercel.vercel.app --token=${{ secrets.VERCEL_TOKEN }}
```

### outputs
各stepsで実行した内容は`steps.[id].outputs.[key]`で参照することができます。

idはstepsで以下のように設定します。
```yml
- name: Deploy to Vercel (preview)
  id: vercel_deploy
```

そこから出力させるために下のように$GITHUB_OUTPUTSを使用します。
下の場合、`key`は`output_${version}`となります。
```yml
 echo "output_${version}=${version}" >> "$GITHUB_OUTPUT"
```

## 1つのworkflowsファイルにまとめる

今回のworkflowsとしてはブランチごとに環境変数を設定して使いまわせるような形にしてみます。

インターン先でjsonを使って比較的綺麗に環境変数をまとめて使う方法を教えてもらったのでそちらを使いたいと思います。

### workflows作成

```deploy-for-vercel.yml
name: deploy-for-vercel.yml
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
  ENV_JSON: | #デプロイする環境によって環境変数を変更する。
    ${{ toJson(  
      fromJson('{
        "staging":{
          "PM": "pnpm",
          "DIRECTORY": "./pnpm",
          "VERCEL_ENV": "preview",
          "VERCEL_URL": "stg-monorepo-deploy-vercel.vercel.app"
        },
        "production":{
          "PM": "yarn",
          "DIRECTORY": "./yarn",
          "VERCEL_ENV": "production",
          "VERCEL_URL": "monorepo-deploy-vercel.vercel.app"
        }
      }')[inputs.environment]) }} #inputsでデプロイする環境を入力
jobs:
  deploy:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ${{ fromJson(env.ENV_JSON).DIRECTORY }} #Jsonから文字列を取り出す
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: ${{ fromJson(env.ENV_JSON).DIRECTORY }}/package.json
      - name: Install pnpm
        if: ${{ fromJson(env.ENV_JSON).DIRECTORY == './pnpm' }}
        uses: pnpm/action-setup@v4
        with:
          version: 10
          run_install: false
      - name: Install dependencies
        run: ${{ fromJson(env.ENV_JSON).PM }} install
      - name: Install vercel CLI
        run: |
          if [ -f yarn.lock ]; then
            yarn global add vercel
          elif [ -f pnpm-lock.yaml ]; then
            pnpm add -g vercel
          fi
      - name: Pull Vercel Environment Information
        run: vercel pull --yes --environment=${{ fromJson(env.ENV_JSON).VERCEL_ENV }} --token=${{ secrets.VERCEL_TOKEN }}
      - name: Build Project Artifacts
        run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
      - name: Deploy to Vercel
        id: vercel_deploy
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
        run: |
          output=$(vercel --token $VERCEL_TOKEN)
          echo "$output"
          echo "url=$output" >> $GITHUB_OUTPUT
      - name: Set Vercel Alias
        run: |
          vercel alias set ${{ steps.vercel_deploy.outputs.url }} ${{ fromJson(env.ENV_JSON).VERCEL_URL }} --token=${{ secrets.VERCEL_TOKEN }}
```

### JSONの使い方
JSONを使うためにactionsで用意されてる関数を使用します。

|メソッド|役割
|---|---
|fromJson|文字列をJSONにエンコード
|toJson|JSONを文字列にデコード

今回の場合は[でkeyを選択することで、ネストされているJSONを取り出しています。

```yml
${{ fromJSON(jsonString)[key] }}
```

そこからtoJsonを使って文字列に変換し、環境変数で設定できるようにしています。

>JSONオブジェクトから文字列に変換することをシリアライズと言います。


```yml
  ENV_JSON: | #デプロイする環境によって環境変数を変更する。
    ${{ toJson(  
      fromJson('{
        "staging":{
          "PM": "pnpm",
          "DIRECTORY": "./pnpm",
          "VERCEL_ENV": "preview",
          "VERCEL_URL": "stg-monorepo-deploy-vercel.vercel.app"
        },
        "production":{
          "PM": "yarn",
          "DIRECTORY": "./yarn",
          "VERCEL_ENV": "production",
          "VERCEL_URL": "monorepo-deploy-vercel.vercel.app"
        }
      }')[inputs.environment]) }} #inputsでデプロイする環境を入力
```

### workflowを使う

下のようなymlを書くことでworkflowsを使うことができます。

これはtriggerのような役割をして、どのブランチに対してpushやPRなどの特定の操作を行ったら、workflowsをどのような環境で使用するかを指定するかを定義できます。
```deploy-stg.yml
name: deploy-stg.yml
on:
  push:
    branches:
      - develop

jobs:
  deploy:
    uses: ./.github/workflows/deploy-for-vercel.yml
    secrets: inherit #secretsを引き継ぐことができる
    with:
      environment: staging
```

workflowファイルはjobsでしか呼び出せないため、stepsには書かず、jobsに書いています。

また、workflowsにsecretsの情報を引き継ぐために`secrets: inherit`を記述しています。

# 最後に

結局、組織アカウントのプライベートリポジトリはGithubEnterprise等の課金をしてないとsecretsが使えないため上のやり方は断念しました。
>チキショー

ただ、jsonの使い方は面白く使えるところが多いと思うのでぜひ参考にしてみてください。