Viteで.env.localで管理している環境変数を GitHub Actions 上でのビルドに反映する

## はじめに

Vite でビルドしている Vue 3 アプリ（Firebase Authentication や Firestoreを使用）を Firebase Hosting にデプロイしており、GitHub Actions と統合して Pull Request / Merge 契機でプレビュー環境構築や本番デプロイを自動で行えるようにしています。

https://firebase.google.com/docs/hosting/github-integration

このとき、.env.localファイルで管理しているgit管理対象外の環境変数を GitHub Actions 上でのビルド環境に反映する方法がよくわからなかったので、とりあえず動く状態にするまでにしたことをメモしておきます。

主な環境変数は、以下のように使用している Firebase の config object の値なので、これを例として記載しています。

```tsx
import { FirebaseOptions } from 'firebase/app';

export const firebaseConfig: FirebaseOptions = {
  apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
  authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
  storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
  appId: import.meta.env.VITE_FIREBASE_APP_ID,
  measurementId: import.meta.env.VITE_MEASUREMENT_ID
};
```

これらの値を.env.localファイルで管理しており、.env.localファイルは.gitignoreで管理対象外になっています。

https://ja.vitejs.dev/guide/env-and-mode.html#env-variables

ただ、Firebase の config object の値は結局クライアントバンドルには含まれますし、非公開にする必要はないようなので、.env.productionファイルに記述すればよいのかもしれません。

https://firebase.google.com/docs/projects/api-keys

なので、機密情報ではないもののGitHubでそのまま公開はしたくない情報を扱う方法という感じです。
Firebase Hosting のランタイム環境に環境変数を設定する方法もなさそうですし、対応の方向性からおかしいかもしれないので、あくまで参考情報として見ていただければと思います。


## 事前準備

### Firebase Hosting のセットアップ

以下のようにFirebase Hosting のセットアップが済んでおり、手動デプロイも問題なくできている状態となっています。

```bash
$ firebase login 

$ firebase init hosting

$ npm run build

$ firebase deploy --only hosting
```

### GitHub統合のセットアップ

以下のコマンドで、Firebase 側のサービスアカウント作成や github への Secrets の登録、GitHub Actions 用のyamlの生成などが自動で行われるはずです。

```bash
$ firebase init hosting:github
```

余談ですが、firebase-toolsをPCローカルに直接インストールせず、firebase-toolsをインストールしたDockerコンテナ上で実行していたため、GitHubからのリダイレクトができず、途中で止まってしまいました。
`firebase login` 時も同じようなことが起こるものの、その場合は `firebase login -—no-localhost` で手動遷移させれば可能なのですが、ここについてはそういうオプションがあるのかわかりませんでした。
以下のように手動で設定する方法もあるようですが、面倒だったので、諦めてPCローカルにfirebase-toolsをグローバルインストールして実行してしまいました。その方がemulator等も使いやすそうですし。。。

https://github.com/FirebaseExtended/action-hosting-deploy/blob/main/docs/service-account.md

成功時は以下です。

```bash
$ firebase init hosting:github

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /path/to/firebase-test

Before we get started, keep in mind:

  * You are initializing within an existing Firebase project directory

=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

i  Using project firebase-test (firebase-test)

=== Hosting:github Setup

i  Detected a .git folder at /path/to/firebase-test
i  Authorizing with GitHub to upload your service account to a GitHub repository's secrets store.

Visit this URL on this device to log in:
https://github.com/login/oauth/authorize?client_id=......

Waiting for authentication...

✔  Success! Logged into GitHub as yngittest

? For which GitHub repository would you like to set up a GitHub workflow? (format: user/repository) xxxxxx/firebase-test

✔  Created service account github-action-xxxxxxxxxxxx with Firebase Hosting admin permissions.
✔  Uploaded service account JSON to GitHub as secret FIREBASE_SERVICE_ACCOUNT_COMIC_HISTORY.
i  You can manage your secrets at https://github.com/xxxxxx/firebase-test/settings/secrets.

? Set up the workflow to run a build script before every deploy? Yes
? What script should be run before every deploy? npm ci && npm run build

✔  Created workflow file /path/to/firebase-test/.github/workflows/firebase-hosting-pull-request.yml
? Set up automatic deployment to your site's live channel when a PR is merged? Yes
? What is the name of the GitHub branch associated with your site's live channel? main

✔  Created workflow file /path/to/firebase-test/.github/workflows/firebase-hosting-merge.yml

i  Action required: Visit this URL to revoke authorization for the Firebase CLI GitHub OAuth App:
https://github.com/settings/connections/applications/xxxxxxxxxxxxxxxxxxxxxxx
i  Action required: Push any new workflow file(s) to your repo

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete!

# 以下2ファイルが追加されている
$ git status
On branch firebase_github_integration
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .github/workflows/firebase-hosting-merge.yml
		.github/workflows/firebase-hosting-pull-request.yml
```

自動生成されたyamlは以下です。

```yaml:.github/workflows/firebase-hosting-merge.yml
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on merge
'on':
  push:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_COMIC_HISTORY }}'
          channelId: live
          projectId: firebase-test
```

```yaml:.github/workflows/firebase-hosting-pull-request.yml
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
  build_and_preview:
    if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_COMIC_HISTORY }}'
          projectId: firebase-test
```


## GitHub Actions ジョブ実行

上記yamlを github に push し、PullRequest を作成すると、ジョブが実行されます。

手動でデプロイする際は、ローカルに.env.localファイルがある状態で、`npm run build` してから `firebase deploy` しているので問題ないのですが、
.env.localファイルは github にはpushされないので、当然 GitHub Actions 上では反映されず、デプロイこそ成功しますが、デプロイされたアプリは API key error となります。

環境変数の値は、githubの該当プロジェクトの Settings → Secrets → Actions で別途登録することができます。

Secretsを環境変数として使用する場合、以下のように書いてみたものの、これではViteでのビルドには反映されませんでした。よく考えれば、ローカルでのビルド時でもシェル上で環境変数をexportしたところで反映されないのと同じです。

```yaml:.github/workflows/firebase-hosting-pull-request.yml
name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
  build_and_preview:
    if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build
        env:
          VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
          VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}
          VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}
          VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }}
          VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }}
          VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }}
          VITE_MEASUREMENT_ID: ${{ secrets.VITE_MEASUREMENT_ID }}
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_COMIC_HISTORY }}'
          projectId: firebase-test
```

代わりに良い方法がないか探してみましたが見つかりませんでした。冒頭にも書いた通り、そもそも対応の方向性からしておかしいのかもしれません。
そこで、無理矢理な方法ですが、ビルド環境上で.envファイルを作成してしまうことにしてみました。

```yaml:.github/workflows/firebase-hosting-pull-request.yml
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
  build_and_preview:
    if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: create .env file
        run: |
          echo "VITE_FIREBASE_API_KEY=${{ secrets.VITE_FIREBASE_API_KEY }}" >> .env
          echo "VITE_FIREBASE_AUTH_DOMAIN=${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}" >> .env
          echo "VITE_FIREBASE_PROJECT_ID=${{ secrets.VITE_FIREBASE_PROJECT_ID }}" >> .env
          echo "VITE_FIREBASE_STORAGE_BUCKET=${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }}" >> .env
          echo "VITE_FIREBASE_MESSAGING_SENDER_ID=${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }}" >> .env
          echo "VITE_FIREBASE_APP_ID=${{ secrets.VITE_FIREBASE_APP_ID }}" >> .env
          echo "VITE_MEASUREMENT_ID=${{ secrets.VITE_MEASUREMENT_ID }}" >> .env
      - run: npm ci && npm run build
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_COMIC_HISTORY }}'
          projectId: firebase-test
```

とりあえずこれでビルド・デプロイが成功するようになりました。

ちなみに、yaml上での複数行コマンドについては、以下を参考にさせていただきました。

https://dev.classmethod.jp/articles/ideas-when-you-want-to-handle-multiple-lines-of-string-in-github-actions/


## まとめ

GitHub Actions のビルドコマンド内で、Vite向けの.envファイルをSecretsを元に作成して使用できるようにしました。

git管理したくない値を取り込むための逃げ道としては許容できるかもしれませんが、Firebaseのconfig objectの値などは、そもそも.env.localで管理すべき情報なのかというところから見直す方がよさそうです。