アプリケーションに記載のAPPのバージョンをGitHub Actionsで自動で更新する

フロントエンドはReact.js, バックエンドはRailsでAPIとして利用しているような環境で働いています。

フロントエンドを新しくリリースしたが、ブラウザキャッシュやCDNがキャッシュを返していてリリースされたバージョンが反映されていないかも!?
みたいなことが最近ありました。

で、このときの調査が大変だったので、ユーザーが見ているバージョンは何であるかわかるようにしたいなとか考えました。

ってことで
- フロントエンドはpackage.jsonに記載のバージョンを自動で更新させよう
- APIでも、現在のバージョンを返すAPIを生やしておこう

としました。
このバージョンを、GitHub Actionsを使って自動で更新させたら便利だったので紹介です。


## 完成系はこんな感じです

![スクリーンショット 2023-12-09 22.57.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/f17c3481-997d-efc8-5186-e0063fe0d6c2.png)

![スクリーンショット 2023-12-09 22.58.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/2972c55b-6fca-e3e0-9460-ec916c2bd0ac.png)


## やること

前提ですが、自分はGitHubのリリース機能を利用しています。
Releaseがpublished(公開）されたら、リリースフローが走る、という流れです。

なので、リリースががされたら、次のバージョンに更新するCIを動かすことにしました。


### フロントエンド

いきなりCIを書いてしまいます。

.github/workflows/update-version.yml 

```yml
name: Update Version

on:
  release:
    types: [published]

jobs:
  update-version:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: main

      - name: Set up Git user
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
    
      - name: Update version file
        run: |
          npm version patch

      - name: push
        run: git push
```

それほど難しないですね。
バージョンの更新は、 `npm version patch` で行っています。
このコマンドを実行をすると、
- package.jsonのバージョンが更新される
- package-lock.jsonのバージョンが更新される
- バージョン名のコミットが作成される

と上記の3つが行われます。


#### ここからは余談

ここで更新されたバージョンをReact.jsでも表示させてあげたかったので、それもやってしまいます。
自分はWebpackを利用しているので、その場合で書きます。

webpack.config.js
```js
const webpack = require("webpack");

module.exports = {
  ...,
  pluguins: [
    ...,
    new webpack.DefinePlugin({
      "process.env.APP_VERSION": JSON.stringify(
        process.env.npm_package_version,
      ),
    }),
  ],
}
```

`process.env.npm_package_version`でバージョンは取得できるので、それを環境変数に組み込むようなコードです。

これを書いておけば、アプリケーション側で、以下のようなコードでバージョンが表示されます。
```tsx
<strong>App Version:</strong> {process.env.APP_VERSION}
```

自分の場合は、こんな感じでユーザーのブラウザの情報を表示させる画面とか作っています。そこに載せました。

![スクリーンショット 2023-12-09 23.13.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/71394cd4-48c3-166c-6434-1cb46dde065c.png)

### バックエンド

こっちは `npm run patch`のようなコマンドがないので、もう少し工夫が必要です。

まず、バージョンを管理しているファイルはここです。

lib/version.rb

```ruby
module YourAppName
  VERSION = "1.1.41".freeze
end
```

Controller側でこのようにして表示していますね。

```ruby
require "version"

class Api::VersionsController < Api::BaseController
  def show
    render json: { version: YourAppName::VERSION }
  end
end
```
![スクリーンショット 2023-12-09 22.58.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/8caded9d-3636-b3f3-0830-55125c8c294b.png)


で、CI側では `lib/version.rb`の

```ruby
module YourAppName
  VERSION = "1.1.41".freeze
end
```
ここのVERSIONの値を更新させます。

### 実際のCI

詳しく見ていきます。
```yml
name: Update Version

on:
  release:
    types: [published]

jobs:
  update-version:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: main
          fetch-depth: 0

      - name: Get latest version
        id: get_version
        run: |
          LATEST_VERSION=$(git tag --sort=-creatordate | head -1 | sed 's/^v//')
          echo "Latest version: $LATEST_VERSION"
          echo "::set-output name=version::${LATEST_VERSION}"

      - name: Calculate next version
        id: calc_next_version
        run: |
          # バージョン番号を分割（例: 1.0.0 -> 1, 0, 0）
          echo "Version: ${{ steps.get_version.outputs.version }}"
          IFS='.' read -ra VERSION_PARTS <<< "${{ steps.get_version.outputs.version }}"
          MAJOR=${VERSION_PARTS[0]}
          MINOR=${VERSION_PARTS[1]}
          PATCH=${VERSION_PARTS[2]}
          echo "Major: $MAJOR, Minor: $MINOR, Patch: $PATCH"

          # パッチバージョンをインクリメント
          let PATCH+=1

          # 次のバージョンを計算
          NEXT_VERSION="$MAJOR.$MINOR.$PATCH"
          echo "Next version: $NEXT_VERSION"
          echo "::set-output name=next_version::${NEXT_VERSION}"
    
      - name: Update version file
        run: |
          sed -i "s/VERSION = \".*\"/VERSION = \"${{ steps.calc_next_version.outputs.next_version }}\"/" lib/version.rb

      - name: Commit and push
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add lib/version.rb
          git commit -m "Update version to ${{ steps.calc_next_version.outputs.next_version }}"
          git push
```

#### 最初のStep(Checkout)

```yml
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: main
          fetch-depth: 0
```
これはいつもやるcheckoutです。
このとき、`ref: main`にしておいて、mainブランチで動かすように明示しています。
また、git tagでタグ一覧も取得できるようにするため。`fetch-depth: 0`も書きます。

#### 2番目のステップ(最新のバージョン取得)

```yml
      - name: Get latest version
        id: get_version
        run: |
          LATEST_VERSION=$(git tag --sort=-creatordate | head -1 | sed 's/^v//')
          echo "Latest version: $LATEST_VERSION"
          echo "::set-output name=version::${LATEST_VERSION}"
```

`git tag --sort=-creatordate | head -1` このコマンドで、最新のバージョンを取得します。
また`v1.0.0`のように私はしているので、vを消すように置換しそれをLATEST_VERSIONという変数に入れます。


### 3番目のステップ(次のバージョン番号を作る)

```yml
      - name: Calculate next version
        id: calc_next_version
        run: |
          # バージョン番号を分割（例: 1.0.0 -> 1, 0, 0）
          echo "Version: ${{ steps.get_version.outputs.version }}"
          IFS='.' read -ra VERSION_PARTS <<< "${{ steps.get_version.outputs.version }}"
          MAJOR=${VERSION_PARTS[0]}
          MINOR=${VERSION_PARTS[1]}
          PATCH=${VERSION_PARTS[2]}
          echo "Major: $MAJOR, Minor: $MINOR, Patch: $PATCH"

          # パッチバージョンをインクリメント
          let PATCH+=1

          # 次のバージョンを計算
          NEXT_VERSION="$MAJOR.$MINOR.$PATCH"
          echo "Next version: $NEXT_VERSION"
          echo "::set-output name=next_version::${NEXT_VERSION}"
```

ここでやっていることは、

1. 2番目のステップで取得した`2.1.0`のようなバージョンを、2と1と0に分割して、VERSION_PARTSという変数に入れます。
1. パッチバージョンがあるVERSION_PARTS[2]の数字に1つ加えます(0 + 1をする)
1. NEXT_VERSIONという変数にバッチバージョンを更新した状態で代入(2.1.1)が代入される

です。

### 4番目のステップ(lib/version.rbの更新)

```yml
      - name: Update version file
        run: |
          sed -i "s/VERSION = \".*\"/VERSION = \"${{ steps.calc_next_version.outputs.next_version }}\"/" lib/version.rb
```
sedを使って新しいバージョンへ置換するようにしています。

### 5番目のステップ(commitしてpush)

```yml
      - name: Commit and push
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add lib/version.rb
          git commit -m "Update version to ${{ steps.calc_next_version.outputs.next_version }}"
          git push
```

これで完成です。

## おわりに
今回はリリースがされたら、というのをトリガーにして次のバージョンへの更新を行いました。
組織によってはリリースの流れが違うと思うので、その際はちょっとずつ変更しながら使ってください。

自分の組織では、ユーザーに届いているバージョンが確認できるようになったことは、とてもお問合せの際に役立つのでよかったなと思います。